Course outline

7.4

Database Security and RDS Proxy

Encryption at rest and in flight, IAM authentication, security groups, audit logs, and connection pooling with RDS Proxy.

26 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Explain what RDS and Aurora encryption at rest covers, when it must be chosen, and the rules for read replicas, snapshots and cross-Region copies.
  • Encrypt an existing unencrypted database (snapshot → encrypted copy → restore) and share an encrypted snapshot with another account.
  • Force TLS on every engine, and explain the RDS certificate authorities and the CA rotation of 2024.
  • Choose between password, Secrets Manager, IAM database authentication and Kerberos, and know the limits of each.
  • Lock the database into the network with private subnets, a DB subnet group and security groups that reference the app tier.
  • Pick the right audit tool: database logs in CloudWatch Logs, Database Activity Streams or CloudTrail.
  • Explain RDS Proxy: connection pooling for Lambda, multiplexing and pinning, faster failover, IAM and Secrets Manager, and its limits.
  • Recognise every exam pattern for database security and RDS Proxy.

#Security in layers

Lessons 7.1 to 7.3 built the databases. This lesson locks them down and then fixes the most common way serverless applications break them. Database security on AWS is a stack of independent layers; an attacker has to get through all of them, and the exam asks about each one separately.

LayerQuestion it answersMain tools
NetworkWho can even reach port 3306 or 5432?Private subnets, DB subnet group, security groups, Publicly accessible = No
AuthenticationWho may log in, with what proof?Master user and passwords, Secrets Manager, IAM database authentication, Kerberos
Encryption at restIs the data on disk, in backups and snapshots unreadable without a key?KMS, TDE
Encryption in flightCan anyone on the network read the queries and results?TLS, rds.force_ssl, require_secure_transport
AuditWho did what, and when?Database logs to CloudWatch Logs, Database Activity Streams, CloudTrail
Connection managementCan a burst of clients knock the database over?RDS Proxy

Everything below applies to RDS and Aurora alike unless a line says otherwise.

#Encryption at rest

#What gets encrypted

When you tick Enable encryption (CLI: --storage-encrypted), RDS encrypts with AES-256 using a key from AWS KMS:

  • the instance’s storage volumes,
  • its logs,
  • its automated backups and snapshots,
  • its read replicas.

The engine and your application see ordinary data; RDS encrypts and decrypts underneath, with a very small performance cost. It is server-side encryption, like SSE-KMS on S3. Lesson 5.2 showed how KMS envelope encryption works for EBS; RDS does exactly the same with a data key per volume.

Encryption is available for every engine and practically every current instance class (only a few very old classes, such as db.t2.micro, M1 and M2, lack it).

#The key: AWS managed or customer managed

AWS managed key aws/rdsCustomer managed key (CMK)
Who creates itRDS, automatically, one per account per RegionYou, in KMS
Key policyFixed by AWS, you cannot edit itYou write it
Share with another accountNoYes, through the key policy
Disable, schedule deletion, choose rotationNoYes
CostNo monthly key feeAbout $1 per key per month plus API requests
Use whenOne account, no sharing, nothing specialSnapshots must be shared, separation of duties, auditors want control of the key

If you do not name a key, RDS uses aws/rds. That default is the root of the most common sharing failure, shown below.

#The rules: choose at creation, and it sticks

  • Encryption can only be turned on when the instance is created. There is no modify option to encrypt an existing instance.
  • It can never be turned off, and the KMS key of an encrypted instance cannot be changed in place.
  • A snapshot of an unencrypted instance is unencrypted; a snapshot of an encrypted instance is encrypted with the same key.
  • Read replicas follow the primary: an unencrypted primary cannot have an encrypted replica, and an encrypted primary cannot have an unencrypted one. In the same Region the replica uses the primary’s key.
  • KMS keys are Regional. A cross-Region read replica or a cross-Region snapshot copy of an encrypted database needs a KMS key in the destination Region.
  • You cannot restore an unencrypted snapshot into an encrypted RDS instance directly; the encryption is added by copying the snapshot.
  • Replication traffic between a primary and its replicas is encrypted in transit even for unencrypted databases, including across Regions.

#Encrypting an existing unencrypted database

The question the notes call the most important trick: “You have an unencrypted database and now it must be encrypted. How?” You cannot encrypt it in place, so you build an encrypted copy of it:

  1. Take a manual snapshot

    Stop the application’s writes first (or plan to catch them up), then create a manual snapshot of the unencrypted instance. This snapshot is unencrypted.

  2. Copy the snapshot with encryption

    Copy the snapshot and choose Enable encryption with a KMS key (aws rds copy-db-snapshot --kms-key-id …). This copy step is where encryption is added. Pick a customer managed key if the snapshot may ever be shared.

  3. Restore a new instance from the encrypted copy

    The restored instance is encrypted, and so is everything derived from it from now on: backups, snapshots, replicas, logs.

  4. Switch the application and clean up

    The new instance has a new endpoint. Point the application at it (or rename the instances so the old endpoint name moves to the new one), check it, recreate any read replicas, and delete the old instance.

Two practical details. Writes made after the snapshot are lost unless you stopped them, so either take a short maintenance window or use AWS DMS to replicate the changes from the old instance to the new one until you cut over. And the same snapshot-copy route is how you change the KMS key of an already encrypted database.

Side notes (newer options): an RDS Blue/Green deployment can now create the green environment encrypted (or with a different key) and switch over in about a minute, and Aurora lets you restore an unencrypted cluster snapshot straight into an encrypted cluster by naming a KMS key at restore time. The exam still expects the classic answer: snapshot → copy with encryption → restore.

Encrypting an existing unencrypted database

There is no switch that encrypts a running RDS instance. Watch the snapshot → encrypted copy → restore path, what happens when you share a snapshot encrypted with the AWS managed key, and what a disabled key does.

Scenario

The classic exam answer, step by step

shop-dbunencryptedno encrypt switch
KMSyour key
Step 1 of 5
1

shop-db was created without encryption. Encryption at rest can only be chosen when an instance is created; modify-db-instance has no option to turn it on later.

#Sharing an encrypted snapshot with another account

Sharing a snapshot (lesson 7.2) gives another account permission to copy or restore it. With encrypted snapshots:

  1. A snapshot encrypted with the AWS managed key aws/rds cannot be shared, because no one can add another account to that key’s policy.
  2. Re-copy the snapshot with a customer managed key, and add the target account to the key policy (kms:Decrypt, kms:DescribeKey, kms:CreateGrant).
  3. Share the snapshot (modify-db-snapshot-attribute --attribute-name restore --values-to-add 444455556666).
  4. The target account copies it, ideally re-encrypting with a key of its own, and restores. Otherwise its database keeps depending on your key: if you disable the key, its database stops too.

Encrypted snapshots can never be made public.

#When the key goes away

The key is part of the database’s availability. If someone disables the KMS key, RDS loses access; about 2 hours later the instance is stopped in the state inaccessible-encryption-credentials-recoverable. Re-enable the key and start the instance within 7 days and it recovers; after that the state becomes terminal and only a restore from backup helps. Instances that cannot be stopped (read replicas, instances with replicas) go straight to the terminal state. Never schedule deletion of a key a database still uses: once a key is deleted, every backup encrypted with it is lost for good.

#TDE for Oracle and SQL Server

Transparent Data Encryption (TDE) is encryption performed by the database engine itself. RDS supports it for Oracle (an option of Oracle Advanced Security) and SQL Server (Enterprise Edition and newer Standard Edition versions), enabled through an option group. TDE can run together with RDS storage encryption, at a small performance cost, with separate keys for each. Pick it when a compliance rule or a migrated application specifically requires engine-level encryption; otherwise RDS encryption at rest is the simpler answer.

Self-checkQuestion

A company runs an unencrypted RDS for MySQL instance. A new policy requires all databases to be encrypted at rest with a customer managed KMS key. What should a solutions architect do?

#Encryption in flight

#TLS and the RDS certificate authorities

Encryption in flight protects queries and results on the network between the client and the database. Every RDS and Aurora engine supports TLS (people still say SSL). The database presents a server certificate signed by an RDS certificate authority (CA); the client should trust that CA and verify the certificate, so it knows it is talking to the real database and not to a man in the middle.

CANotes
rds-ca-rsa2048-g1The default. RSA 2048, valid until 2061
rds-ca-rsa4096-g1RSA 4096, stronger key, slightly slower handshakes
rds-ca-ecc384-g1Elliptic curve (ECC) 384, fast and strong; needs a modern client
  • Clients download the certificate bundle (global-bundle.pem, or a per-Region bundle) and connect with full verification, for example sslmode=verify-full in PostgreSQL clients or --ssl-mode=VERIFY_IDENTITY in the MySQL client.
  • With these CAs, RDS rotates the server certificate automatically before it expires. Trust only the root CA in your application, never intermediate certificates.
  • The CA rotation of 2024: the old rds-ca-2019 CA expired in August 2024. Every instance had to switch to one of the new CAs, and every client that verifies certificates had to trust the new bundle; applications that pinned the old certificate broke on the day. That is the kind of incident an exam question may describe as “connections fail after the CA was changed”.
  • RDS Proxy uses certificates from ACM instead, so clients of a proxy do not need the RDS bundle.

Note on the notes: database traffic normally stays inside the VPC, not on the internet, but it still crosses shared network infrastructure, and compliance rules (PCI DSS, HIPAA) require it to be encrypted anyway.

#Forcing TLS

By default most engines accept TLS but do not require it. To refuse plain-text connections, set a parameter in the DB parameter group (for Aurora, the DB cluster parameter group):

EngineHow to require TLS for every connectionDefault
PostgreSQL (RDS and Aurora)rds.force_ssl = 11 (on) from PostgreSQL 15, 0 on older versions
SQL Serverrds.force_ssl = 1 (needs a reboot)0
MySQL / MariaDB (RDS and Aurora MySQL)require_secure_transport = ONOFF on most versions, ON in Aurora MySQL 8.4
OracleAdd the SSL option to an option group (TLS on its own port, typically 2484)Off
Per user, MySQLALTER USER 'app'@'%' REQUIRE SSL;Not required

A correction to the notes: for MySQL they give REQUIRE SSL. That is a per-user clause in CREATE USER / ALTER USER, not a parameter. The instance-wide switch that matches PostgreSQL’s rds.force_ssl is require_secure_transport. Either works on the exam if the question is about one user; for “all connections”, choose the parameter.

#Authentication: who may log in

#The master user and passwords

Every instance is created with a master user that has broad (but not superuser) rights: on RDS you never get the true root of the engine. Applications should use their own, less privileged database users. Passwords are the default way to log in, and their weakness is obvious: they end up in config files, environment variables and Git history, and nobody rotates them.

Secrets Manager integration (since late 2022). Tick Manage master credentials in AWS Secrets Manager (CLI --manage-master-user-password) and RDS generates the master password, stores it as a secret and rotates it every 7 days by default. Nobody needs to know it; applications and people fetch it from Secrets Manager with IAM permissions, and every read is in CloudTrail. You can do the same for application users with a Secrets Manager rotation function.

#IAM database authentication

With IAM database authentication, an application logs in without any password. It uses its IAM identity instead:

  1. You turn on IAM authentication for the instance and create a database user that authenticates through IAM: in MySQL CREATE USER app_user IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';, in PostgreSQL CREATE USER app_user; GRANT rds_iam TO app_user;.
  2. An IAM policy allows rds-db:connect on arn:aws:rds-db:us-east-1:111122223333:dbuser:db-ABCDEFGHIJKL01234/app_user (the instance’s resource ID and the database user).
  3. The application, running with an EC2 instance role, a Lambda execution role or an ECS task role, asks the SDK for an authentication token (aws rds generate-db-auth-token). The SDK signs it locally with the role’s temporary credentials (SigV4); no API call is made.
  4. It connects over TLS, sending the token as the password. RDS checks the signature and the permission with IAM.

Facts to remember:

  • The token is valid for 15 minutes, and it is checked only at login: an open connection keeps working after the token expires, but every new connection needs a fresh token.
  • Engines: MySQL, MariaDB and PostgreSQL (RDS and Aurora). Not Oracle, SQL Server or Db2; those use Kerberos.
  • TLS is required for IAM connections.
  • Access is managed centrally in IAM: revoke the role’s permission and no new connection succeeds anywhere.
  • Limits. IAM authentication costs the database CPU and memory (AWS asks for 300 to 1,000 MiB of spare memory) and is meant for moderate connection rates. AWS used to recommend it below about 200 new connections per second; current documentation drops the number but keeps the warning. For a high connection rate, put RDS Proxy in front: clients authenticate to the proxy with IAM and the proxy keeps long-lived connections.
  • CloudTrail does not record token generation (it is a local signature).

#Kerberos and Microsoft Active Directory

For companies that already manage users in Active Directory, RDS supports Kerberos authentication through AWS Directory Service for Microsoft Active Directory (AWS Managed Microsoft AD, which can trust an on-premises AD). Users log in with their corporate account and single sign-on. Supported on SQL Server, Oracle, MySQL, PostgreSQL and Db2 (and Aurora MySQL/PostgreSQL). It is the answer when a question says “Windows authentication”, “use existing Active Directory accounts” or names SQL Server or Oracle with centralised identities.

MethodEnginesCredentialBest for
PasswordAllLong-lived passwordSimple setups, the master user
Password in Secrets ManagerAllPassword, rotated automaticallyAny app that must use passwords; RDS Proxy
IAM database authenticationMySQL, MariaDB, PostgreSQL15-minute token from an IAM roleEC2, Lambda, ECS apps with no stored secrets
Kerberos (Managed Microsoft AD)SQL Server, Oracle, MySQL, PostgreSQL, Db2Corporate AD accountEnterprises with Active Directory

#Network: who can reach the database

Authentication only matters after a packet reaches the database. Lessons on VPCs and security groups (Ch03–04) already taught the tools; this is how they are used for a database:

  • Private subnets. Databases live in private subnets with no route from an internet gateway.
  • DB subnet group. RDS places instances (and Multi-AZ standbys) in the subnets of the DB subnet group you choose; give it private subnets in at least two AZs.
  • Publicly accessible = No. With No, the endpoint resolves to a private address only. With Yes, RDS gives it a public IP; even then the security group and the subnet’s route decide whether anyone can reach it. Leave it at No for production.
  • Security groups that reference the app tier. The database’s security group allows only the database port from the application’s security group:
Inbound rule on db-sgPortSource
PostgreSQL5432sg-app (the app tier’s security group)

The notes make exactly this point: do not allow a fixed IP such as 10.0.0.5/32. Reference the EC2 instances’ security group ID, and every new instance that Auto Scaling launches with that group is allowed automatically, while anything else in the VPC is not. Default ports: MySQL/MariaDB/Aurora MySQL 3306, PostgreSQL 5432, SQL Server 1433, Oracle 1521. Changing the port is not security; the security group is.

  • NACLs can add a subnet-level deny, but security groups do the real work.
  • VPC endpoints. The RDS management API (create, modify, snapshot) can be reached privately through an interface VPC endpoint (com.amazonaws.<region>.rds). It carries API calls only; database connections always go straight to the instance’s endpoint inside the VPC.
  • Getting in as a human. Developers reach a private database through AWS Client VPN, Site-to-Site VPN, or a Session Manager port-forwarding session to an instance in the VPC, never by making the database public.

Who can reach the database, and how they log in

The database sits in a private subnet. Its security group allows port 5432 only from the app tier’s security group. Follow a normal app connection, a blocked laptop and a blocked batch server, an IAM token login, and a client that refuses to use TLS.

Scenario

VPC10.0.0.0/16
Private subnet
Laptop203.0.113.7
app-1app-sg
batch-1batch-sg
orders-dbdb-sg5432 from app-sg
Step 1 of 3
1

db-sg has one inbound rule: TCP 5432 from the security group app-sg. Not an IP address like 10.0.1.25/32, but the group itself.

#No SSH, except RDS Custom

RDS is a managed service: AWS runs the operating system, so there is no SSH and no remote desktop into the host. You change behaviour through parameter groups and option groups, read logs through the console or CloudWatch, and see OS metrics through Enhanced Monitoring.

The notes’ exam trick: if a question says “need OS-level access”, “install a custom agent on the database host” or “run custom scripts on the host”, standard RDS is the wrong answer. Choose RDS Custom (Oracle and SQL Server, where you can connect to the host; lesson 7.2) or run the database on EC2.

#Audit and monitoring

#Database logs → CloudWatch Logs

Each engine writes logs: the error log, slow query log and general log (MySQL), the postgresql log, the Oracle alert and audit files, the SQL Server error and agent logs. For a real audit trail of who ran which statement, enable the engine’s audit feature:

EngineAudit feature
MySQL, MariaDBMariaDB Audit Plugin (option group, MARIADB_AUDIT_PLUGIN); Aurora MySQL has built-in advanced auditing (server_audit_logging)
PostgreSQLpgaudit extension (shared_preload_libraries, pgaudit.log)
OracleNative / unified auditing
SQL ServerSQL Server Audit option (SQLSERVER_AUDIT), files can also go to S3

Logs kept on the instance are limited in size and age. Publish them to CloudWatch Logs (--cloudwatch-logs-export-configuration) and you get:

  • long retention that you choose (from a day to 10 years, or never expire), as the slides say, “for longer retention”;
  • near real-time search with Logs Insights, metric filters and alarms (failed logins, DROP statements);
  • export to S3 or a security analytics tool.

That is the notes’ exam answer for both “retain audit logs for a long time for compliance” and “monitor database logs in near real time”.

#Database Activity Streams

Database Activity Streams push a near real-time stream of database activity to an Amazon Kinesis data stream, encrypted with your KMS key. Supported on Aurora PostgreSQL and Aurora MySQL, RDS for Oracle and RDS for SQL Server. The key property: the stream is configured and controlled outside the database, so even database administrators cannot switch it off or edit it unnoticed. That is the separation of duties auditors want for regulated data. From Kinesis, Firehose stores the events in S3 and security tools analyse them. The feature itself is free; you pay for Kinesis.

#CloudTrail is for API calls, not SQL

CloudTrail records calls to the RDS API: who created, modified, deleted or shared an instance or snapshot, from where. It does not see SQL run inside the database. “Who deleted the snapshot?” → CloudTrail. “Who deleted the rows?” → the database audit log or an activity stream.

#Performance monitoring, briefly

  • CloudWatch metrics: CPU, DatabaseConnections, free memory, IOPS, replica lag.
  • Enhanced Monitoring: OS-level metrics from an agent on the host, down to 1-second granularity.
  • Performance Insights → CloudWatch Database Insights. Performance Insights showed database load by wait event and SQL. In 2025–26 AWS folded it into CloudWatch Database Insights (Standard and Advanced modes); the old Performance Insights console reached end of life on 31 July 2026. Exam questions written earlier still say “Performance Insights” for “find the SQL causing database load”; the answer is the same tool family.
The question asks for…Tool
Who changed or deleted the instance or a snapshotCloudTrail
Which user ran which SQL, kept for yearsEngine audit log → CloudWatch Logs (retention)
Alert within minutes on failed loginsCloudWatch Logs metric filter + alarm
Audit that database administrators cannot tamper withDatabase Activity Streams → Kinesis
Which SQL is slowing the databaseDatabase Insights (Performance Insights)
OS processes and memory on the hostEnhanced Monitoring

Auditing a database: three different records

SQL that runs inside the database, a tamper-proof activity stream, and calls to the RDS API are recorded in three different places. Pick the one the question asks about.

Scenario

Admin
orders-dbaudit onaudit log
CloudTrailAPI calls
CloudWatch Logs
Alarm
delete rows
Step 1 of 3
1

Auditing is not on by default. On MySQL and MariaDB you add the MariaDB Audit Plugin through an option group; on PostgreSQL you load the pgaudit extension. Now every login and statement is written to the audit log.

#RDS Proxy

#The problem: connections are expensive

A relational database does real work for every connection: a TCP and TLS handshake, authentication, and memory for the session. PostgreSQL even starts a separate backend process per connection. So every instance has a ceiling, max_connections, which RDS sets from memory by default: roughly RAM ÷ 9.5 MB for PostgreSQL (capped at 5,000) and RAM ÷ 12 MB for MySQL. A 4 GiB instance therefore allows about 400 PostgreSQL connections.

Long-running servers handle this with a connection pool inside the application: 10 servers × 20 pooled connections = 200, forever. Lambda breaks that model:

  • Every execution environment is its own little process with its own connection. Lambda’s default concurrency is 1,000 per Region.
  • A traffic spike scales Lambda from 10 to 1,000 environments in seconds, so 1,000 connections arrive at once. At 400 the database answers “too many connections” and 600 invocations fail.
  • After the spike, idle environments are frozen, not closed, so their connections stay open and occupied.
  • Opening and closing connections rapidly burns database CPU even when the limit is not reached.

Scaling the database up only to hold more connections is the expensive non-fix.

#What RDS Proxy is

Amazon RDS Proxy is a fully managed database proxy that sits between the application and the database and keeps a pool of database connections that many clients share.

  • Serverless and highly available: it scales with the load, runs across multiple AZs, and there are no instances to manage.
  • VPC only: it must be in the same VPC as the database and is never publicly accessible.
  • Engines: RDS for MySQL, PostgreSQL, MariaDB and SQL Server, and Aurora MySQL and PostgreSQL. Not Oracle, not Db2, not RDS Custom for SQL Server.
  • No code change for most applications: change the endpoint in the connection string. Proxies listen on 3306 (MySQL/MariaDB), 5432 (PostgreSQL) or 1433 (SQL Server).

Lambda connection storm and RDS Proxy

A sale starts and 1,000 Lambda functions run at once against a database whose max_connections is about 400. Compare direct connections with RDS Proxy, then see pinning and how the proxy logs in.

Scenario

VPC
1,000 Lambdasconcurrent
orders-dbmax_connections 400
1,000 connects
Step 1 of 3
1

Each Lambda execution environment opens its own database connection. 1,000 run at the same moment, so 1,000 connections arrive within seconds.

#How the pool works: multiplexing, queueing, pinning

  • Multiplexing. A client holds a database connection only while a transaction runs, then the proxy gives it to another client. If a Lambda run takes 200 ms and spends 20 ms in a transaction, 1,000 concurrent functions need only about 1,000 × 20 ÷ 200 = 100 database connections.
  • Limits you set: MaxConnectionsPercent (default 100% of max_connections) caps what the proxy may open, so other applications and admins keep some headroom; MaxIdleConnectionsPercent controls how many idle ones it keeps warm.
  • Queueing instead of failing. When the pool is busy, a client waits for a free connection up to ConnectionBorrowTimeout (default 120 seconds). Latency rises, but requests are not refused; beyond the limits the proxy sheds load predictably.
  • Pinning. If a session leaves state on a connection (a session-level SET, a temporary table, user variables, some prepared statements, a statement over 16 KB), the proxy cannot share that connection and pins it to the client until the session ends. Heavy pinning turns the proxy back into one connection per client. Watch the DatabaseConnectionsCurrentlySessionPinned metric and move such settings into the proxy’s initialization query.
  • Idle client connections are closed after IdleClientTimeout (default 30 minutes).

Connection storm simulator

Set how many Lambda functions run at once, how big the database is and how much of each run is spent inside a transaction. Then switch RDS Proxy on and off.

Database size (approx. max_connections)

RDS Proxy

Database connections in use400 / 400
Client connections: 1,000 to the databaseRefused: too many connections: 600Waiting in the proxy queue: 0

400 connections fill the database; the other 600 functions get “too many connections” and fail. Idle frozen functions keep holding their connections after the burst.

#Faster failover

When a Multi-AZ primary fails (lesson 7.1), RDS promotes the standby and updates the endpoint’s DNS record; a failover typically takes 60 to 120 seconds. Without a proxy, every application connection breaks, and clients that cache DNS keep trying the dead address for even longer, then all reconnect at once.

With RDS Proxy, the application’s connections end at the proxy, so they stay open. The proxy monitors the database instances directly instead of waiting for DNS, connects to the new primary as soon as it is ready, and lets the waiting queries continue. AWS states this reduces failover time by up to 66% for RDS and Aurora Multi-AZ databases. A transaction that was in progress at the moment of failure is still rolled back, so applications still need retry logic.

Multi-AZ failover with and without RDS Proxy

The primary in AZ a fails and the standby in AZ b takes over. Without a proxy the application depends on DNS; with a proxy it keeps its connections.

Scenario

App
RDS Proxynot used
DNSendpoint
DB, AZ aprimary
DB, AZ bstandby
queries
Step 1 of 5
1

The app connects straight to the instance endpoint, which DNS resolves to the primary in AZ a. The proxy is not used in this scenario.

#Security: IAM in front, Secrets Manager behind

  • IAM authentication can be enforced: clients must connect to the proxy with an IAM token (TLS required), so no database password exists in the Lambda code or environment. The execution role needs rds-db:connect on the proxy.
  • Credentials in Secrets Manager: the proxy logs in to the database with a user name and password it reads from AWS Secrets Manager (up to 200 secrets per proxy). Rotation is picked up without application changes.
  • End-to-end IAM (2025 side note): the proxy can now also use IAM database authentication to the database, with no Secrets Manager secret at all (not for SQL Server).
  • TLS between client and proxy can be required (RequireTLS); the proxy uses ACM certificates.

The notes’ complete answer for “connect serverless functions to RDS securely, without hard-coded credentials, with secrets managed centrally, and without exhausting connections”: Lambda + RDS Proxy + Secrets Manager + IAM authentication.

#Endpoints, quotas and pricing

FactValue
Default endpointRead/write, to the writer (primary)
Extra endpointsUp to 20 per proxy; read-only endpoints send traffic to Aurora replicas (for RDS instances in a replication setup the proxy can only target the writer, not read replicas)
Proxies per account20 by default (adjustable)
TargetsOne DB instance or Aurora cluster per proxy (several proxies can share one database)
PricingPer vCPU-hour of the underlying instances (provisioned), or per ACU-hour for Aurora Serverless; extra endpoints also pay PrivateLink

#When not to bother

An application on long-running servers with a good built-in connection pool, steady traffic and no failover-time requirement gains little. Workloads that pin almost every session gain little too. RDS Proxy shines for Lambda and other short-lived clients, spiky traffic, many small services sharing one database, and failover-sensitive applications.

Self-checkQuestion

A serverless order API on AWS Lambda writes to an RDS for PostgreSQL instance. During flash sales Lambda concurrency jumps to 2,000 and many invocations fail with too many connections errors, although database CPU stays below 40%. The team wants the least operational effort. What should a solutions architect recommend?

#How it fits together in system design

  • Defence in depth: private subnets and app-tier security groups first, then authentication without stored passwords (IAM or Secrets Manager), TLS required by parameter, encryption at rest with a customer managed key, and audit to CloudWatch Logs or an activity stream.
  • Decide encryption on day one. Every production database should be created encrypted, with a customer managed key if snapshots will ever cross accounts (DR copies, a separate backup account, sharing with a partner).
  • Keys are dependencies. Protect KMS keys with tight key policies and alarms on DisableKey and ScheduleKeyDeletion in CloudTrail.
  • Serverless data access: Lambda → RDS Proxy (IAM auth) → database with the secret in Secrets Manager. The same proxy also shortens failovers for the rest of the fleet.
  • Separate who manages from who reads: IAM controls the RDS API, database users control SQL, CloudTrail audits the first, database audit logs or activity streams audit the second.

#Try it yourself

# 1. Is this instance encrypted, and with which key?
aws rds describe-db-instances --db-instance-identifier shop-db \
  --query "DBInstances[0].[StorageEncrypted,KmsKeyId,CACertificateIdentifier,PubliclyAccessible]"

# 2. Encrypt an unencrypted instance: snapshot → encrypted copy → restore
aws rds create-db-snapshot --db-instance-identifier shop-db \
  --db-snapshot-identifier shop-db-plain
aws rds wait db-snapshot-available --db-snapshot-identifier shop-db-plain
aws rds copy-db-snapshot --source-db-snapshot-identifier shop-db-plain \
  --target-db-snapshot-identifier shop-db-enc \
  --kms-key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
aws rds wait db-snapshot-available --db-snapshot-identifier shop-db-enc
aws rds restore-db-instance-from-db-snapshot --db-instance-identifier shop-db-encrypted \
  --db-snapshot-identifier shop-db-enc --db-subnet-group-name private-db-subnets \
  --vpc-security-group-ids sg-0db1234567890abcd --no-publicly-accessible

# 3. Share the encrypted snapshot (works only with a customer managed key)
aws rds modify-db-snapshot-attribute --db-snapshot-identifier shop-db-enc \
  --attribute-name restore --values-to-add 444455556666

# 4. Require TLS (PostgreSQL) and use the default CA
aws rds modify-db-parameter-group --db-parameter-group-name pg16-secure \
  --parameters "ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=immediate"
aws rds modify-db-instance --db-instance-identifier shop-db-encrypted \
  --ca-certificate-identifier rds-ca-rsa2048-g1 --apply-immediately

# 5. Managed master password in Secrets Manager, and IAM authentication on
aws rds modify-db-instance --db-instance-identifier shop-db-encrypted \
  --manage-master-user-password --enable-iam-database-authentication --apply-immediately

# 6. Log in with an IAM token over TLS (token valid 15 minutes)
curl -sO https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
TOKEN=$(aws rds generate-db-auth-token --hostname shop-db-encrypted.abcdefgh1234.us-east-1.rds.amazonaws.com \
  --port 5432 --username app_user --region us-east-1)
PGPASSWORD="$TOKEN" psql "host=shop-db-encrypted.abcdefgh1234.us-east-1.rds.amazonaws.com port=5432 \
  dbname=shop user=app_user sslmode=verify-full sslrootcert=global-bundle.pem"

# 7. Publish logs to CloudWatch Logs and keep them for 7 years
aws rds modify-db-instance --db-instance-identifier shop-db-encrypted \
  --cloudwatch-logs-export-configuration '{"EnableLogTypes":["postgresql"]}' --apply-immediately
aws logs put-retention-policy \
  --log-group-name /aws/rds/instance/shop-db-encrypted/postgresql --retention-in-days 2557

# 8. RDS Proxy with IAM required and the secret from Secrets Manager
aws rds create-db-proxy --db-proxy-name shop-proxy --engine-family POSTGRESQL \
  --auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:us-east-1:111122223333:secret:shop-app-AbCdEf","IAMAuth":"REQUIRED"}]' \
  --role-arn arn:aws:iam::111122223333:role/rds-proxy-secrets \
  --vpc-subnet-ids subnet-0aaa1111 subnet-0bbb2222 --vpc-security-group-ids sg-0proxy12345678 \
  --require-tls
aws rds register-db-proxy-targets --db-proxy-name shop-proxy \
  --db-instance-identifiers shop-db-encrypted
aws rds describe-db-proxies --db-proxy-name shop-proxy --query "DBProxies[0].Endpoint"
-- PostgreSQL: who is connected, and is each session encrypted?
SELECT a.usename, a.client_addr, s.ssl, s.version
FROM pg_stat_activity a JOIN pg_stat_ssl s USING (pid)
WHERE a.backend_type = 'client backend';

-- How close are we to the limit?
SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;

-- An application user for IAM authentication
CREATE USER app_user;
GRANT rds_iam TO app_user;

#Summary

Key takeaways

  • Encryption at rest uses KMS (AES-256) and covers storage, logs, automated backups, snapshots and read replicas. Choose it at creation; it cannot be turned off, and the key cannot be changed in place.
  • Encrypt an unencrypted database: snapshot → copy with encryption → restore → switch (Blue/Green and Aurora restore-with-key are newer shortcuts).
  • Replicas follow the primary’s encryption; cross-Region replicas and copies need a key in the destination Region. Snapshots encrypted with aws/rds cannot be shared: use a customer managed key and its key policy. A disabled key stops the database (7 days to recover).
  • TDE is engine-level encryption for Oracle and SQL Server.
  • In flight: TLS with RDS CAs (default rds-ca-rsa2048-g1; rds-ca-2019 expired in 2024). Force it with rds.force_ssl=1 (PostgreSQL, SQL Server) or require_secure_transport=ON (MySQL/MariaDB) in the parameter group; MySQL REQUIRE SSL is per user.
  • IAM database authentication: MySQL, MariaDB, PostgreSQL; 15-minute token, checked at login, TLS required, no stored password. Secrets Manager rotates passwords (managed master password, 7 days). Kerberos with Managed Microsoft AD for SQL Server, Oracle and others.
  • Network: private subnets, DB subnet group in two AZs, Publicly accessible = No, database security group allowing the port from the app tier’s security group.
  • No SSH, except RDS Custom; otherwise EC2.
  • Audit: engine audit logs → CloudWatch Logs (retention, alarms); Database Activity Streams → Kinesis for tamper-resistant audit; CloudTrail for API calls, not SQL.
  • RDS Proxy: managed, serverless, Multi-AZ connection pool, VPC only; MySQL, PostgreSQL, MariaDB, SQL Server and Aurora. Fixes Lambda “too many connections”, cuts failover time by up to 66%, enforces IAM auth with credentials in Secrets Manager. Watch pinning; priced per vCPU or ACU.