AWS RDS and Aurora Security Hardening: Encryption, IAM Auth, and VPC Isolation

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
AWS RDS and Aurora security failures follow a predictable pattern: static master passwords stored in environment variables, security groups open to broad CIDR ranges, no encryption key management, and no audit logging. Each of these is a configuration choice AWS leaves to the operator, and the defaults are not hardened. This guide covers the full hardening checklist from a practitioner standpoint: KMS CMK encryption with key policy controls, IAM database authentication replacing static passwords, Secrets Manager rotation for credentials that cannot use IAM auth, parameter group settings that enable connection and query audit logging, private subnet placement, and deletion protection. Aurora-specific considerations are called out where they differ from standard RDS.
Enforce Encryption at Rest with Customer-Managed KMS Keys
RDS encryption at rest must be configured at instance creation time. Customer-managed keys (CMK) give you control over key access policy, rotation, and the ability to audit all decrypt operations via CloudTrail.
Create a dedicated CMK for RDS with a restrictive key policy
Create a KMS key dedicated to RDS encryption: `aws kms create-key --description 'RDS encryption key' --key-policy file://rds-key-policy.json`. The key policy should grant key usage only to the RDS service principal (`rds.amazonaws.com`), the specific IAM roles running database operations, and the security team role. Deny key deletion to all except the key admin with a condition requiring MFA. A broad key policy that grants key usage to any IAM user in the account defeats the purpose of a CMK.
Enable encryption at instance creation
Specify the CMK when creating the RDS instance: `aws rds create-db-instance --storage-encrypted --kms-key-id arn:aws:kms:<region>:<account>:key/<key-id>`. For Aurora clusters: `aws rds create-db-cluster --storage-encrypted --kms-key-id <key-arn>`. Verify encryption is active: `aws rds describe-db-instances --query 'DBInstances[*].{ID:DBInstanceIdentifier, Encrypted:StorageEncrypted, KMS:KmsKeyId}'`. Any instance with `StorageEncrypted: false` is a finding.
Configure annual automatic CMK rotation
Enable automatic key rotation on the RDS CMK: `aws kms enable-key-rotation --key-id <key-id>`. KMS rotates the key material annually while maintaining decryption capability for data encrypted with previous key versions. This satisfies most compliance frameworks' key rotation requirements without requiring re-encryption of existing data -- KMS handles the rotation transparently. Tag the key with `Environment:production` and `DataClassification:restricted` for resource inventory and access policy purposes.
Enforce TLS in Transit and Require SSL Connections
Encrypting data in transit prevents credential and query interception on the VPC network. The approach differs between PostgreSQL and MySQL engines.
Enforce SSL for PostgreSQL RDS via parameter group
Create a custom parameter group for the RDS instance and set `rds.force_ssl=1`. This rejects any connection that does not use SSL. Apply via: `aws rds modify-db-parameter-group --db-parameter-group-name <group-name> --parameters ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=immediate`. Restart the instance to apply. Verify by attempting a psql connection without SSL: `psql 'host=<endpoint> sslmode=disable'` should return `FATAL: no pg_hba.conf entry for host` or a connection refused error.
Enforce TLS for Aurora MySQL via require_secure_transport
For Aurora MySQL, set `require_secure_transport=ON` in the cluster parameter group. This is the Aurora equivalent of MySQL's `--require-ssl` flag. Update application connection strings to include `ssl_ca=<rds-ca-bundle.pem>` and verify the server certificate. Download the RDS CA bundle from `https://truststore.pki.rds.amazonaws.com/<region>/global-bundle.pem` and distribute it to application servers. Certificate verification (not just encryption) prevents man-in-the-middle attacks on the VPC network.
Rotate to the latest RDS CA certificate bundle
AWS periodically updates the RDS CA certificate. Check which CA your instances use: `aws rds describe-db-instances --query 'DBInstances[*].{ID:DBInstanceIdentifier, CA:CACertificateIdentifier}'`. Instances using `rds-ca-2019` must be updated to `rds-ca-rsa2048-g1` or `rds-ca-rsa4096-g1`. Schedule the certificate rotation: `aws rds modify-db-instance --db-instance-identifier <id> --ca-certificate-identifier rds-ca-rsa2048-g1 --apply-immediately`. Update application trust stores before applying to avoid connection failures.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Replace Static Passwords with IAM Database Authentication
IAM database authentication eliminates persistent database passwords for applications running with IAM roles. This is the highest-value credential hardening step for RDS.
Enable IAM authentication on the RDS instance
Enable IAM database authentication: `aws rds modify-db-instance --db-instance-identifier <id> --enable-iam-database-authentication --apply-immediately`. For Aurora clusters: `aws rds modify-db-cluster --db-cluster-identifier <id> --enable-iam-database-authentication`. This does not break existing password-authenticated connections -- it adds IAM auth as an additional authentication method.
Create IAM-authenticated database users
For PostgreSQL: connect using the master password and run `CREATE USER appuser WITH LOGIN; GRANT rds_iam TO appuser; GRANT CONNECT ON DATABASE appdb TO appuser;`. For MySQL: `CREATE USER 'appuser'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'appuser'@'%';`. The application IAM role needs `rds-db:connect` permission with resource ARN `arn:aws:rds-db:<region>:<account>:dbuser:<db-resource-id>/appuser`.
Update applications to generate IAM auth tokens
Replace hardcoded connection strings with token generation code. In Python using boto3: `token = rds_client.generate_db_auth_token(DBHostname=endpoint, Port=5432, DBUsername='appuser')`. Pass the token as the password field in the database connection. The token is valid for 15 minutes -- generate a fresh token per connection or connection pool initialization rather than caching it. Most connection pool libraries support a custom auth token provider callback for this pattern.
Use Secrets Manager for services that cannot use IAM auth
Services running outside AWS (on-premises, other clouds) cannot generate IAM auth tokens. For these, store credentials in Secrets Manager with automatic rotation enabled. Configure the rotation Lambda to use the `awsSecretsManagerRotationSingleUser` or `awsSecretsManagerRotationMultiUser` strategy. Multi-user rotation maintains two alternating users, ensuring zero-downtime rotation. Applications call `GetSecretValue` using their IAM role -- the secret ARN is stable, credentials rotate on the configured schedule.
Configure Audit Logging and Enable Deletion Protection
Database audit logging and deletion protection close two critical gaps: the inability to investigate post-breach query history, and accidental or malicious database deletion.
Enable pgaudit for PostgreSQL audit logging
Add the `pgaudit` extension to the RDS parameter group: set `shared_preload_libraries=pgaudit` and `pgaudit.log=ddl,write,role`. Apply and restart. Then in the database: `CREATE EXTENSION pgaudit;`. pgaudit logs all DDL (schema changes), DML writes (INSERT, UPDATE, DELETE), and role changes to PostgreSQL logs, which RDS exports to CloudWatch Logs. Export these logs to a SIEM or S3 for long-term retention and investigation.
Enable deletion protection on all production instances
Enable deletion protection: `aws rds modify-db-instance --db-instance-identifier <id> --deletion-protection --apply-immediately`. For Aurora clusters: `aws rds modify-db-cluster --db-cluster-identifier <id> --deletion-protection`. Create an AWS Config rule that flags any RDS instance or Aurora cluster without deletion protection as NON_COMPLIANT. This control is trivial to enable and prevents both accidental IaC mistakes and malicious deletion by a compromised IAM identity.
Configure automated backup retention to 35 days
Set backup retention to the maximum 35 days for production databases: `aws rds modify-db-instance --db-instance-identifier <id> --backup-retention-period 35`. This maximizes the point-in-time recovery window for ransomware incidents where database data is corrupted or deleted. Enable the final snapshot option: `--no-skip-final-snapshot --final-db-snapshot-identifier <snapshot-name>` to ensure a snapshot is taken before any delete command completes, even if deletion protection is somehow disabled.
The bottom line
AWS RDS and Aurora security hardening converges on three priorities: eliminate static passwords with IAM database auth or Secrets Manager rotation, enforce encryption in transit and at rest with CMK keys, and place instances in private subnets with security groups scoped to application server security group IDs. The parameter group audit logging and deletion protection controls are low-effort but high-value additions that most teams skip. Audit your RDS fleet monthly with AWS Config rules checking for public accessibility, missing encryption, and absence of deletion protection.
Frequently asked questions
Can you enable encryption on an existing unencrypted RDS instance?
You cannot enable encryption directly on an existing unencrypted RDS instance. The process requires: (1) create an encrypted snapshot of the unencrypted instance using the AWS console or CLI with `--kms-key-id` specified, (2) restore a new encrypted instance from the encrypted snapshot, (3) update your application connection strings to point to the new instance, (4) delete the original unencrypted instance after verifying the encrypted instance works. This is a brief maintenance window operation. For Aurora, you can use AWS Database Migration Service (DMS) to migrate to a new encrypted Aurora cluster with minimal downtime.
How does IAM database authentication work for RDS PostgreSQL?
IAM database authentication for RDS PostgreSQL works by generating a temporary authentication token using the AWS CLI or SDK: `aws rds generate-db-auth-token --hostname <endpoint> --port 5432 --username <db-user>`. The token is a signed URL valid for 15 minutes. The application presents this token as the database password when connecting. The RDS instance verifies the token's signature against AWS IAM. The database user must be created with the `rds_iam` role: `CREATE USER appuser WITH LOGIN; GRANT rds_iam TO appuser;`. The application's IAM role needs the `rds-db:connect` permission for the specific database resource ARN.
What RDS parameter group settings are most important for security?
For PostgreSQL RDS: set `log_connections=1` and `log_disconnections=1` to log all connection events, `log_min_duration_statement=1000` to log slow queries (which often indicate injection attempts), and `pgaudit.log=ddl,write` via the pgaudit extension for comprehensive audit logging. For MySQL/Aurora MySQL: enable `general_log=1` for connection logging, `log_error_verbosity=3` for detailed error logging, and `require_secure_transport=ON` to enforce TLS. Apply these in a custom parameter group -- the default parameter group cannot be modified and does not enforce these settings.
How do you configure RDS security groups to follow least privilege?
Create a dedicated security group for the RDS instance with inbound rules allowing only the application server security group IDs (not CIDR ranges) on the database port (5432 for PostgreSQL, 3306 for MySQL). Reference security group IDs rather than IP ranges: `--source-group <app-server-sg-id>` instead of `--cidr 10.0.0.0/8`. This ensures that as application server IPs change, the security group rule remains accurate. Never add 0.0.0.0/0 or ::/0 as an inbound source -- any finding of this type should be treated as a critical misconfiguration.
What is the difference between RDS automated backups and manual snapshots for security?
RDS automated backups are retained for 1-35 days (configure the maximum for sensitive databases) and are automatically deleted when the instance is deleted -- unless you enable the final snapshot option. Manual snapshots persist indefinitely until explicitly deleted. For security purposes: automated backups provide point-in-time recovery for ransomware scenarios; manual snapshots provide long-term retention for compliance. Both inherit the KMS encryption of the source instance. To protect against backup deletion by a compromised admin account, use AWS Backup with a backup vault and a Backup Vault Lock policy preventing backup deletions for a defined retention period.
How do you rotate RDS credentials automatically with Secrets Manager?
Enable Secrets Manager rotation by creating a secret for the RDS credentials (Secrets Manager detects RDS endpoints and offers automatic rotation setup). Choose the rotation interval (7-30 days for most environments, 1 day for high-security). Secrets Manager uses a Lambda rotation function that creates a new password, updates it in the database, updates the secret, and optionally maintains a previous-version grace period. Applications retrieve credentials via `GetSecretValue` API call rather than from config files -- the secret ARN is stable, the password rotates automatically. IAM database auth is preferable to rotated static passwords but Secrets Manager rotation is a significant improvement over unrotated static credentials.
Should RDS instances have a public IP address?
No. RDS instances should never be publicly accessible (`PubliclyAccessible: false`). Place instances in private VPC subnets with no route to the internet gateway. Application servers in public or application-tier subnets access the database via internal VPC routing. For database administration access, use AWS Systems Manager Session Manager port forwarding to tunnel a local client connection through SSM without opening any inbound security group rules: `aws ssm start-session --target <ec2-bastion-id> --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters host=<rds-endpoint>,portNumber=5432,localPortNumber=5432`. This eliminates bastion host SSH exposure entirely.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
