AWS RDS Security
Securing an RDS database. Network isolation, encryption in transit and at rest, IAM authentication, secret rotation, audit logging and deletion protection.
A database holds the data everything else exists to serve, which makes it the most valuable target in most architectures. The controls below are layered deliberately. Each assumes the ones before it might fail.
Network isolation first
Never make an RDS instance publicly accessible. It is a setting, it defaults to off for instances in private subnets, and it is occasionally turned on during troubleshooting and forgotten.
# Audit every instance in the account
aws rds describe-db-instances \
--query 'DBInstances[].{ID:DBInstanceIdentifier,Public:PubliclyAccessible,Encrypted:StorageEncrypted}' \
--output table
A publicly accessible database is found by automated scanners within hours. Combined with a weak password or a known engine vulnerability, that is how databases get ransomed.
Place instances in private subnets with no route to an internet gateway, and reference the application's security group rather than a CIDR range:
aws ec2 authorize-security-group-ingress \
--group-id sg-database --protocol tcp --port 5432 \
--source-group sg-application
The rule then survives instance churn and expresses the intent, the application tier may reach the database tier, rather than encoding a set of addresses that will change.
For the strongest isolation, put databases in a third subnet tier with no internet route at all, not even through NAT. A compromised database then cannot exfiltrate outward.
Encryption at rest
Must be enabled at creation. It cannot be added later, and the migration path, snapshot, copy encrypted, restore, cut over, means downtime.
It costs nothing, so there is no reason any instance should be unencrypted.
aws rds create-db-instance ... --storage-encrypted --kms-key-id alias/payments-rds
A customer-managed KMS key rather than the default aws/rds key gives you a key policy as a second access control layer and visibility of key usage in CloudTrail. It also means you can revoke access to the data by revoking the key.
Encryption covers the instance, its automated backups, snapshots and read replicas. Note that a snapshot of an unencrypted instance is unencrypted, and sharing an encrypted snapshot cross-account requires sharing the KMS key too. A frequent stumbling block.
Encryption in transit
Encryption at rest protects a stolen disk. It does nothing for traffic on the wire.
# PostgreSQL. Require SSL via a parameter group
aws rds modify-db-parameter-group \
--db-parameter-group-name payments-pg16 \
--parameters "ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=pending-reboot"
Then connect with verification, not merely encryption:
psql "host=payments-prod.abc123.eu-west-1.rds.amazonaws.com \
sslmode=verify-full sslrootcert=eu-west-1-bundle.pem \
dbname=payments user=app_user"
sslmode=require encrypts but does not verify the server, which leaves the connection open to interception. verify-full is the one that actually protects you, and it needs the RDS CA bundle for your Region.
Those CA certificates expire. AWS announces rotations well in advance, but an unrotated client trust store fails to connect on the day. It is worth tracking rather than discovering.
Credentials
Three approaches, in increasing order of preference.
A password in Secrets Manager, created and rotated by RDS itself:
aws rds modify-db-instance \
--db-instance-identifier payments-prod \
--manage-master-user-password --apply-immediately
IAM database authentication, where there is no long-lived password at all:
aws rds modify-db-instance --db-instance-identifier payments-prod \
--enable-iam-database-authentication --apply-immediately
TOKEN=$(aws rds generate-db-auth-token \
--hostname payments-prod.abc123.eu-west-1.rds.amazonaws.com \
--port 5432 --username app_user)
Tokens last 15 minutes. Access is granted and revoked through IAM like any other permission, and there is nothing in the application to rotate or leak. The limitation is a connection-rate cap, which is why it pairs well with RDS Proxy.
RDS Proxy with IAM auth is the strongest combination: the proxy holds the credential, the application authenticates with IAM, and connection pooling solves the Lambda connection-exhaustion problem at the same time.
Least privilege inside the database
IAM controls who can reach the database. It says nothing about what they can do once connected, that is the database's own permission system, and it is frequently ignored.
Applications should not connect as the master user. Create a role with only the privileges it needs:
CREATE USER app_user;
GRANT rds_iam TO app_user; -- enable IAM authentication
GRANT CONNECT ON DATABASE payments TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app_user;
-- deliberately no DELETE, no DDL, no superuser
A compromised application credential then cannot drop tables.
Audit logging
Database engine logs are not enabled by default and are the only record of what happened inside the database:
aws rds modify-db-instance \
--db-instance-identifier payments-prod \
--cloudwatch-logs-export-configuration '{"EnableLogTypes":["postgresql","upgrade"]}' \
--apply-immediately
For PostgreSQL, pgaudit gives structured audit logging of statements. For MySQL, the MariaDB audit plugin does the same. Both are enabled through the option or parameter group.
Send logs to CloudWatch and set retention, otherwise they accumulate and are billed indefinitely.
Protecting against deletion
aws rds modify-db-instance \
--db-instance-identifier payments-prod \
--deletion-protection --apply-immediately
Deletion protection requires an explicit disable before an instance can be removed, which turns an irreversible one-step mistake into a deliberate two-step action. Always take a final snapshot when deleting.
Automated backups are deleted with the instance; manual snapshots are not. For anything you must be able to recover, take periodic manual snapshots and copy them to another account. A backup in the same account as the thing it backs up is not protection against a compromised account.
AWS Backup with a vault lock provides genuinely immutable backups that even an administrator cannot delete.
An audit in one command
aws rds describe-db-instances --query 'DBInstances[].{
ID:DBInstanceIdentifier,
Public:PubliclyAccessible,
Encrypted:StorageEncrypted,
IAMAuth:IAMDatabaseAuthenticationEnabled,
DeleteProtect:DeletionProtection,
Backups:BackupRetentionPeriod,
MultiAZ:MultiAZ}' --output table
Any Public: true, Encrypted: false, DeleteProtect: false or Backups: 0 is worth investigating immediately. AWS Config and Security Hub can check these continuously rather than relying on someone remembering.
Quick reference
| Task | Command |
|---|---|
| Security audit | aws rds describe-db-instances --query 'DBInstances[].{ID:DBInstanceIdentifier,Public:PubliclyAccessible,Encrypted:StorageEncrypted}' |
| Enable IAM auth | aws rds modify-db-instance --db-instance-identifier <id> --enable-iam-database-authentication |
| Generate a token | aws rds generate-db-auth-token --hostname <e> --port 5432 --username <u> |
| Managed password | aws rds modify-db-instance --db-instance-identifier <id> --manage-master-user-password |
| Deletion protection | aws rds modify-db-instance --db-instance-identifier <id> --deletion-protection |
| Export logs | aws rds modify-db-instance --db-instance-identifier <id> --cloudwatch-logs-export-configuration ... |
| Manual snapshot | aws rds create-db-snapshot --db-instance-identifier <id> --db-snapshot-identifier <name> |
Key takeaways
- Never publicly accessible. Private subnets, and a security group referencing the application's group.
- Encryption at rest is creation-only. It is free, so there is no excuse for an unencrypted instance.
sslmode=requiredoes not verify the server. Useverify-fullwith the RDS CA bundle.- IAM database authentication removes the long-lived password entirely: tokens last 15 minutes.
- Applications must not connect as the master user. Grant only the privileges they need.
- Engine audit logs are off by default and are the only record of in-database activity.
- Enable deletion protection and take a final snapshot. Manual snapshots survive instance deletion; automated backups do not.
- Copy critical snapshots to another account. A backup beside the original is not protection.
Check your understanding
Encrypted but not protected
An application connects to RDS with sslmode=require. Is the connection secure against interception?
Where backups should live
Why copy critical RDS snapshots to a separate AWS account?
Frequently Asked Questions
Should an RDS instance ever be publicly accessible?
Essentially never. A public database is discovered by automated scanners within hours, and combined with a weak credential or an unpatched engine vulnerability that is how databases get ransomed. Place instances in private subnets with no internet gateway route, set --no-publicly-accessible, and reach them through a bastion, Session Manager port forwarding, or a VPN. Audit the whole account periodically, because the setting is occasionally enabled during troubleshooting and forgotten.
What is the difference between sslmode=require and verify-full?
require guarantees the connection is encrypted but performs no verification of the server's identity, so an attacker able to intercept traffic can present their own certificate and read everything. verify-full validates the certificate chain against the RDS CA bundle for your Region and confirms the hostname matches. Since downloading the bundle is a one-off step, verify-full should be the default for anything carrying real data.
How does IAM database authentication work?
You enable it on the instance and grant the rds_iam role to a database user. Applications then call generate-db-auth-token to obtain a 15-minute token and use it as the password. There is no long-lived credential in the application, access is granted and revoked through IAM like any other permission, and every token request appears in CloudTrail. The main limitation is a cap on new connections per second, which makes it a natural pairing with RDS Proxy.
Can I enable encryption on an existing RDS instance?
No. It is fixed at creation. The migration is snapshot, copy the snapshot specifying a KMS key, restore from the encrypted copy and cut over, which means downtime or a replication exercise. Because encryption adds no cost, every instance should be created encrypted. Using a customer-managed KMS key rather than the default also gives you a key policy as a second access layer and visibility of key usage in CloudTrail.
Should my application connect as the RDS master user?
No. The master user typically has broad privileges including DDL, so a compromised application credential can drop tables rather than merely read data. Create a dedicated database role granting only what the application needs, usually SELECT, INSERT and UPDATE on specific schemas, and often not DELETE. IAM controls who can reach the database; the database's own permission system controls what they can do once connected, and skipping the second layer wastes the first.
How should I back up an RDS database securely?
Automated backups give point-in-time recovery within a retention window of up to 35 days, but they are deleted along with the instance. Manual snapshots persist independently, so take them for anything you must be able to recover. Critically, copy important snapshots to a separate AWS account. A backup in the same account as the database shares its blast radius, so whoever can delete one can usually delete both. AWS Backup with a locked vault provides immutability that even an administrator cannot override.