How to Connect to a Private Amazon RDS Instance Securely
RDS instances belong in private subnets with no internet route. Which access pattern you need depends on who is connecting: EC2 apps connect directly inside the VPC, developers use Session Manager from their laptops, and Lambda functions benefit from RDS Proxy.
Simple explanation
- An RDS instance in a private subnet has no internet-facing route. Nothing on the internet can reach it directly, regardless of passwords. That is the goal.
- Application servers on EC2 or ECS in the same VPC connect directly over private IP. No tunnel, no proxy needed. Just a security group rule.
- Developers on a laptop use Session Manager port forwarding. It creates an encrypted tunnel through an EC2 instance in the VPC, with no SSH keys, no open inbound ports, and no bastion host to maintain.
- Lambda functions and serverless workloads benefit from RDS Proxy, which pools database connections so hundreds of concurrent Lambda invocations do not exhaust the connection limit.
- Always enforce TLS. Traffic inside a VPC is not encrypted at the application layer by default. TLS protects credentials and query data in transit.
- Store credentials in Secrets Manager or use IAM database authentication. Hardcoded passwords in environment variables are a common source of leaks.
How secure RDS connectivity works
When you create an RDS instance, it gets a DNS endpoint like mydb.xxxx.eu-west-2.rds.amazonaws.com. That hostname resolves to a private IP address inside your VPC. If the instance lives in a private subnet with no internet gateway route, that address is only reachable from inside the VPC.
Four things control who can connect:
Private subnet placement means the database has no internet-facing route. Even if someone knows the endpoint hostname, it does not resolve to a reachable address from the public internet. See Subnets in AWS: Public, Private, and Isolated for how subnet types differ.
Security groups act as a stateful firewall attached to the RDS instance. A security group rule on the RDS instance should allow inbound traffic on the database port (5432 for PostgreSQL, 3306 for MySQL) only from the security group attached to your application servers. Not from a CIDR range. Not from the internet. This is called a security group reference: when a server joins the application security group, it automatically gains database access. When it leaves, access is revoked.
TLS/SSL encrypts the connection between the client and the database. TLS is the modern successor to SSL; you will see both terms used, but new connections use TLS. Without it, credentials and query results travel in plaintext across your network.
Authentication controls who is allowed in after the network permits the connection. This is either a database username and password, or short-lived IAM tokens generated on demand.
Which access pattern should you choose?
| Pattern | Best for | Public exposure | Operational overhead | Recommended? |
|---|---|---|---|---|
| Direct from EC2/ECS in same VPC | Application servers | None | Minimal | Yes. Default for app traffic. |
| Session Manager port forwarding | Developer laptops, one-off maintenance | None | Low (IAM permissions + SSM agent) | Yes. Default for human access. |
| Bastion host / SSH tunnel | Teams that cannot use Session Manager | Bastion host is public-facing | Medium (patching, key management) | Legacy fallback only. |
| RDS Proxy | Lambda, serverless, connection spikes | None | Low (managed service) | Yes. For Lambda and spiky workloads. |
| VPN / Direct Connect | On-prem or corporate network access | None (private network path) | High (network infrastructure) | Yes. When you have hybrid connectivity. |
Use direct private access for application traffic, Session Manager for developer access, and RDS Proxy only when connection management is the actual problem. You do not need all three at the same time. Most teams running standard EC2 or ECS apps start with the first two and never need the third.
Direct connection from EC2 or ECS in the same VPC#
The simplest pattern and the right default for application traffic. Your EC2 instance or ECS task runs in the same VPC as RDS. Add one security group rule that allows the application tier’s security group to reach the database port. No tunnels, no proxies, no extra components.
# Allow the application security group to connect to RDS on port 5432 (PostgreSQL)
aws ec2 authorize-security-group-ingress \
--group-id sg-rds-id \
--protocol tcp \
--port 5432 \
--source-group sg-app-tier-idThe RDS endpoint resolves to a private IP within the VPC. Your application connects using the standard hostname with no special routing needed:
psql -h mydb.xxxx.eu-west-2.rds.amazonaws.com -U dbadmin -d myappdbFor MySQL, the port changes to 3306; the pattern is identical. See Running MySQL on Amazon RDS or PostgreSQL on Amazon RDS for full setup guides.
Session Manager port forwarding from your laptop#
With a bastion host, you carry a physical key (SSH key file) and walk through a public entrance. Anyone watching can see the door exists. With Session Manager, you tap an access card (IAM identity) and the door appears only for you, only when you need it, with no visible entrance from outside.
AWS Systems Manager Session Manager lets you forward a local port to the RDS endpoint through an EC2 instance in your VPC. No SSH keys, no bastion host, no inbound security group rules needed.
Requirements: an EC2 instance in the same VPC as RDS with the SSM agent installed, an IAM instance profile that includes AmazonSSMManagedInstanceCore, and your IAM user or role having ssm:StartSession permission.
# Start a port forwarding session through the EC2 instance to the RDS endpoint
aws ssm start-session \
--target i-0abc1234def56789 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{
"host": ["mydb.xxxx.eu-west-2.rds.amazonaws.com"],
"portNumber": ["5432"],
"localPortNumber": ["5433"]
}'
# In a second terminal, connect via localhost
psql -h localhost -p 5433 -U dbadmin -d myappdbSession start and end events are recorded in AWS CloudTrail, giving you an auditable record of who initiated each session. The content of your queries is not captured.
Bastion host / SSH tunnel#
A bastion host (also called a jump box) is a small EC2 instance in a public subnet. Developers SSH into it and from there can reach private resources inside the VPC. For local database access, you create an SSH tunnel from your laptop through the bastion:
# Tunnel local port 5433 through the bastion to the RDS endpoint on port 5432
ssh -L 5433:mydb.xxxx.eu-west-2.rds.amazonaws.com:5432 \
-i ~/.ssh/my-keypair.pem \
ec2-user@bastion-public-ip \
-N
# Connect via the local tunnel
psql -h localhost -p 5433 -U dbadmin -d myappdbThe RDS security group needs an inbound rule from the bastion’s security group. Not from your IP directly.
Maintaining a bastion means patching a publicly exposed EC2 instance, managing SSH key distribution, and setting up access logging manually. Session Manager gives you the same connectivity with none of that overhead. Use the bastion pattern only when Session Manager is genuinely unavailable.
RDS Proxy#
RDS Proxy is a managed connection pooler that sits between your application and the RDS instance. Applications connect to the proxy endpoint; the proxy maintains a pool of persistent database connections and multiplexes application connections across them.
Without RDS Proxy, every Lambda invocation walks straight to a specific database worker and occupies them fully. With Proxy, all visitors check in at reception first. Reception routes each request to whoever is available, so a hundred visitors can be served by a much smaller team without anyone being overwhelmed.
RDS Proxy is most valuable when:
- Lambda functions open a new database connection on every invocation. With high concurrency, this exhausts the RDS connection limit fast. Proxy multiplexes hundreds of Lambda connections over a small pool of database connections.
- Workloads have sudden connection spikes that the database cannot absorb gracefully.
- Multi-AZ failover transparency matters. Applications reconnect to the proxy endpoint, and the proxy reroutes to the new primary without the application needing to handle reconnection logic.
# Connect to the proxy endpoint instead of the RDS endpoint directly
psql -h myapp-proxy.proxy-xxxx.eu-west-2.rds.amazonaws.com \
-U dbadmin -d myappdbFor serverless VPC access patterns, RDS Proxy is often the right default when Lambda functions need database access.
On-prem or corporate network access via VPN or Direct Connect#
If your team connects from a corporate network or on-premises data centre, you can extend a private network path to AWS using AWS Site-to-Site VPN or AWS Direct Connect. Both give you private routing to the RDS endpoint without exposing it to the internet.
Once in place, traffic flows between your on-premises network and the VPC over the private connection. The RDS security group needs an inbound rule from the CIDR range of your on-premises network.
When to use each option
App server on EC2 or ECS in the same VPC. Use direct private connection with a security group reference. Nothing else needed.
Developer laptop. Use Session Manager port forwarding. No SSH keys, no exposed instance, and session events are auditable in CloudTrail.
Legacy admin workflows where Session Manager is unavailable. Use a bastion host with an SSH tunnel. Restrict the bastion’s inbound rules to known IP ranges and keep it patched.
Lambda or other serverless workloads. Add RDS Proxy in front of the RDS instance. Lambda’s concurrency model creates many short-lived connections; Proxy ensures these do not exhaust the database connection limit.
On-premises or corporate network access. Set up Site-to-Site VPN or Direct Connect and route to the RDS endpoint over the private VPC path.
Session Manager vs bastion host
| Factor | Session Manager | Bastion host |
|---|---|---|
| Inbound ports required | None | SSH (port 22) open to developer IPs |
| SSH keys | Not needed | Required and must be managed |
| Public EC2 instance needed | No | Yes |
| OS patching burden | Normal instance patching (can stay private) | Must patch a publicly exposed instance |
| Access control | IAM policies | SSH key possession |
| Audit trail | Session events recorded in CloudTrail | SSH logs on the bastion (manual setup) |
| Setup effort | IAM policy + SSM agent on EC2 | EC2 instance, security group, key pair |
For teams that have not already invested in bastion infrastructure, Session Manager is the better default in almost every case.
RDS Proxy vs direct connections
| Factor | RDS Proxy | Direct connection |
|---|---|---|
| Connection pooling | Managed by proxy, transparent to the application | Application-level (PgBouncer, HikariCP) |
| Best for Lambda | Yes. Multiplexes concurrent invocations. | No. Each invocation opens a new connection. |
| Multi-AZ failover | Proxy endpoint handles reconnection | Application must detect failover and retry |
| Latency | Small additional hop (roughly 1 ms) | Lowest possible latency |
| IAM auth support | Supported | Supported |
| Cost | Charged per vCPU-hour of the RDS instance | No additional cost |
For EC2 or ECS applications with a fixed number of long-running instances, direct connections with application-level pooling are simpler and sufficient. RDS Proxy solves a specific problem: connection exhaustion from serverless or highly concurrent workloads.
Enforce SSL/TLS
TLS encrypts the connection between your client and the database. Even inside a VPC, this matters: it protects credentials and query data from other services with network visibility, and satisfies compliance requirements that mandate encryption in transit.
Download the RDS certificate bundle and configure your database client to require TLS with full certificate verification:
# Download the global RDS certificate bundle
curl -o /etc/ssl/certs/rds-global-bundle.pem \
https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
# PostgreSQL: enforce TLS with certificate verification
psql "host=mydb.xxxx.eu-west-2.rds.amazonaws.com \
sslmode=verify-full \
sslrootcert=/etc/ssl/certs/rds-global-bundle.pem \
user=dbadmin dbname=myappdb"
# MySQL: enforce TLS with certificate verification
mysql --ssl-ca=/etc/ssl/certs/rds-global-bundle.pem \
--ssl-verify-server-cert \
-h mydb.xxxx.eu-west-2.rds.amazonaws.com \
-u admin -pUsing sslmode=require in PostgreSQL encrypts the connection but does not verify the server certificate. This leaves you open to man-in-the-middle attacks. Use sslmode=verify-full with the RDS certificate bundle to confirm you are connected to the right server.
Client-side settings alone are not enough. Enforce TLS at the database level so non-SSL connections are rejected outright, not just discouraged:
- PostgreSQL: Set
rds.force_ssl=1in the RDS parameter group. - MySQL: Set
require_secure_transport=ONin the RDS parameter group.
Secrets Manager vs IAM database authentication
Both approaches avoid storing a static database password in application code. They solve the problem differently.
AWS Secrets Manager stores the database username and password as a managed secret. Your application retrieves the secret at startup using the AWS SDK. Secrets Manager can rotate the secret automatically on a schedule, updating the database password without requiring a deployment. This works with any language and any database user. It is the easier default for most teams.
IAM database authentication replaces the password entirely. Your application calls the AWS API to generate a short-lived auth token (valid for 15 minutes) and uses it as the password when connecting. There is no stored credential to leak. This works with MySQL and PostgreSQL on RDS.
# Enable IAM authentication on the RDS instance
aws rds modify-db-instance \
--db-instance-identifier myapp-postgres-prod \
--enable-iam-database-authentication \
--apply-immediately
# Generate a short-lived auth token
TOKEN=$(aws rds generate-db-auth-token \
--hostname mydb.xxxx.eu-west-2.rds.amazonaws.com \
--port 5432 \
--region eu-west-2 \
--username iam_app_user)
# Connect using the token as the password (TLS is required for IAM auth)
PGPASSWORD=$TOKEN psql \
"host=mydb.xxxx.eu-west-2.rds.amazonaws.com \
user=iam_app_user dbname=myappdb \
sslmode=verify-full \
sslrootcert=/etc/ssl/certs/rds-global-bundle.pem"The generate-db-auth-token API call is recorded in AWS CloudTrail as a management event, giving you an auditable record of when tokens were requested. Individual SQL queries are not captured by CloudTrail.
For IAM roles attached to EC2 instances or ECS tasks, IAM database authentication integrates cleanly. The instance profile provides the credentials to generate the token with no stored secret to manage or rotate.
Which to choose: start with Secrets Manager if you want automatic rotation and broad compatibility. Add IAM database authentication when you want to eliminate stored credentials entirely, particularly for EC2 or ECS workloads with IAM instance profiles. See Rotating Secrets in AWS for how automatic rotation works in practice.
Common mistakes
- Opening port 3306 or 5432 to
0.0.0.0/0in the RDS security group.This exposes your database to the internetThis creates a public attack surface regardless of which subnet the instance is in. Always reference the application tier’s security group as the source. See Security Groups in AWS Explained for how security group references work.
- Making RDS publicly accessible for convenience. Enabling
—publicly-accessiblebecause it is faster than setting up SSM is one of the most common early AWS mistakes. Use Session Manager port forwarding instead. It is more secure, requires no inbound ports, and has no ongoing maintenance burden. - Setting up a bastion host when Session Manager would be simpler. Bastion hosts require a publicly exposed EC2 instance, SSH key management, and regular OS patching. Session Manager gives you the same connectivity with none of that.
- Not verifying TLS certificates. Using
sslmode=require(PostgreSQL) or—ssl(MySQL) encrypts the connection but does not verify the server certificate. Usesslmode=verify-fullwith the RDS certificate bundle to prevent man-in-the-middle attacks. - Using the master RDS credentials in application code. The master user is for administrative tasks. Create a dedicated database user for your application with only the permissions it needs, and store those credentials in Secrets Manager.
- Storing long-lived credentials without rotation. Credentials in environment variables or config files that are never rotated are a persistent leak risk. Use Secrets Manager with automatic rotation, or switch to IAM database authentication to eliminate static passwords.
- Assuming IAM auth removes the need for connection management. IAM database authentication eliminates static passwords but does not pool connections. Lambda functions using IAM auth still need RDS Proxy to avoid exhausting the database connection limit under concurrency.
Summary
- Place RDS instances in private subnets. Nothing on the internet should be able to reach the database port directly.
- Application servers on EC2 or ECS connect directly within the VPC using security group references. This is the simplest and lowest-latency pattern.
- Developers on laptops should use Session Manager port forwarding. No SSH keys, no exposed instance, and session events are auditable in CloudTrail.
- Lambda functions and serverless workloads benefit from RDS Proxy to prevent connection exhaustion under concurrency.
- Always enforce TLS at the database level:
rds.force_ssl=1for PostgreSQL,require_secure_transport=ONfor MySQL. - Use Secrets Manager for automatic credential rotation, or IAM database authentication to eliminate static passwords entirely.
- Publicly accessible RDS is a tightly restricted exception, not a default. Use it only with a strict security group, SSL enforcement, and active monitoring.
Frequently asked questions
Can I connect to a private RDS instance from my laptop?
Yes. The recommended way is AWS Systems Manager Session Manager port forwarding. It creates an encrypted tunnel from your laptop to the RDS instance through an EC2 instance in the same VPC, with no SSH keys, no bastion host, and no open inbound ports required. If your team already has a VPN or AWS Direct Connect, you can connect through that private network path instead.
Is it ever okay to make RDS publicly accessible?
Rarely, and only under strict controls: SSL enforced, a strong password, a security group that allows only specific known IP addresses, and monitoring for unexpected connections. For production databases, the answer is almost always no. The attack surface exposed by a public database port is too large to justify the convenience. Use Session Manager port forwarding for developer access instead.
When should I use RDS Proxy instead of direct connections?
RDS Proxy is most valuable for Lambda functions and other serverless compute where each invocation opens a new database connection. Without Proxy, a Lambda function with significant concurrent invocations can exhaust your RDS connection limit quickly. For EC2 or ECS applications with a small number of long-running instances, application-level connection pooling such as PgBouncer for PostgreSQL or HikariCP for Java is usually sufficient and simpler than adding RDS Proxy.
Should I use IAM database authentication or Secrets Manager?
Both avoid storing a static password in application code. Secrets Manager stores credentials securely and rotates them automatically, making it the easier default for most teams. IAM database authentication eliminates stored credentials entirely by using short-lived tokens generated from your AWS credentials, but requires more application-level plumbing. Start with Secrets Manager; add IAM auth when you want to remove static credentials completely.
Do I still need SSL inside a VPC?
Yes. Network traffic inside a VPC is not automatically encrypted at the application layer. SSL/TLS encrypts the connection between your application and the database, protecting credentials and query data from visibility to other services in the network. Enforce SSL at the database level so that non-SSL connections are rejected rather than just discouraged.