Securely Accessing an Amazon RDS Database Across VPCs Using RDS Proxy and NLB
Secure method for establishing connections to Amazon Aurora or Amazon RDS databases situated between VPCs and external clients - CLI Implementation Guide
Overview
Organizations use multiple VPCs to separate workloads and environments. While databases are typically managed in a dedicated VPC, teams need secure access from other VPCs and external locations. This solution enables secure database access without complex networking setups like VPC peering or Transit Gateway.
This guide provides CLI instructions for securely accessing an Amazon RDS database across multiple VPCs using RDS Proxy and Network Load Balancer (NLB). The solution enables cross-VPC database connectivity without complex VPC peering. The solution works across all RDS database engines (PostgreSQL: 5432, MySQL: 3306, SQL Server: 1433) with minimal configuration changes.

Architecture Components
- VPC A: Contains RDS database and RDS Proxy in private subnets
- VPC B: Hosts cross-VPC RDS Proxy endpoint and NLB in public subnets
- Security: Managed through security groups and AWS Secrets Manager
- DNS: Optional Route 53 configuration for custom domain resolution
Prerequisites
Required Components
- AWS CLI v2 installed and configured with appropriate credentials
- IAM permissions for:
- RDS
- EC2
- Route 53
- Secrets Manager
- Network Load Balancer
- Two VPCs (VPC A and VPC B) with private and public subnets
- Database client tools
- Amazon RDS database in VPC A's private subnet
AWS CLI Configuration
Bash
aws configure --profile your-profileaws --version
Implementation Steps
Step 1: Configure RDS Proxy in VPC A
Verify Database Setup
Bash
aws rds describe-db-instances \--region <region> \--db-instance-identifier my-aurora-cluster-instance-1 \--query 'DBInstances[0].DBSubnetGroup.Subnets[*].SubnetIdentifier' \--profile <your-profile>
Create Database User
Connect to your database and execute:
For PostgreSQL:
SQL
CREATE USER proxy_user WITH PASSWORD 'secure_password';GRANT ALL PRIVILEGES ON DATABASE mydb TO proxy_user;
For MySQL:
SQL
CREATE USER 'proxy_user'@'%' IDENTIFIED BY 'secure_password';GRANT ALL PRIVILEGES ON mydb.* TO 'proxy_user'@'%';
For SQL Server:
SQL
USE [mydb];CREATE LOGIN proxy_user WITH PASSWORD = 'secure_password';CREATE USER proxy_user FOR LOGIN proxy_user;GRANT ALL TO proxy_user;
Create Secret in Secrets Manager
Bash
aws secretsmanager create-secret \--name my-rds-proxy-secret \--secret-string '{"username":"proxy_user","password":"secure_password","engine": "postgres","host": "<host_endpoint>","port": <database_port>,"dbname": "db_name"}' \--region <region> \--profile <your-profile>
Note: Replace host_endpoint with your RDS database endpoint, database port with correct port and db_name with database name of your database
Create Security Group in VPC A for RDS Proxy and add inbound rule to it.
Bash
aws ec2 create-security-group \--group-name rds-proxy-sg \--description "Security group for RDS Proxy in VPC A" \--vpc-id <vpc-id-vpc-a> \--region <region> \--profile <your-profile>aws ec2 authorize-security-group-ingress \--group-id sg-<rds-proxy-sg-id> \--protocol all \--source-group sg-<rds-proxy-sg-id> \--region <region> \--profile <your-profile>
Note: Replace sg-<rds-proxy-sg-id> in the above command with the actual security group ID returned from the above create security group command.
Create RDS Proxy
Bash
aws rds create-db-proxy \--db-proxy-name my-rds-proxy \--engine-family POSTGRESQL \--role-arn arn:aws:iam::<account-id>:role/my-rds-proxy-role \--vpc-subnet-ids subnet-<private-subnet-id-1> subnet-<private-subnet-id-2> \--auth '[{"AuthScheme": "SECRETS","SecretArn": "arn:aws:secretsmanager:<region>:<account-id>:secret:my-rds-secret","IAMAuth": "DISABLED","Description": "Proxy database credentials"}]' \--security-group-ids sg-<rds-proxy-sg-id> \--region <region> \--profile <your-profile>
Note: Adjust --engine-family to MYSQL, POSTGRESQL, or SQLSERVER based on your RDS engine.
Register Database Target
Register the RDS database as a target for the RDS Proxy.
Bash
aws rds register-db-proxy-targets \--db-proxy-name my-rds-proxy \--target-group-name default \--db-cluster-identifiers my-aurora-cluster \--region <region> \--profile <your-profile>
Note: Use --db-cluster-identifiers for Aurora or --db-instance identifiers for non-Aurora RDS.
Step 2: Create Cross-VPC RDS Proxy Endpoint in VPC B
This configuration enables secure access to your RDS Proxy from VPC B without complex networking setups.
Create Security Group and add inboud rule for Cross-VPC Endpoint
Bash
aws ec2 create-security-group \--group-name cross-vpc-proxy-sg \--description "Security group for cross-VPC RDS Proxy endpoint in VPC B" \--vpc-id <vpc-id-vpc-b> \--region <region> \--profile <your-profile>aws ec2 authorize-security-group-ingress \--group-id sg-<cross-vpc-proxy-sg-id> \--protocol all \--source-group sg-<cross-vpc-proxy-sg-id> \--region <region> \--profile <your-profile>
Note: Replace sg-<cross-vpc-proxy-sg-id> in the above command with the actual security group ID returned from the above create security group command.
Create Cross-VPC Endpoint
Create a cross-VPC endpoint for the RDS Proxy with at least two subnets in VPC B and use describe proxy endpoints to verify the status.
Bash
aws rds create-db-proxy-endpoint \--db-proxy-name my-rds-proxy \--endpoint-name my-cross-vpc-endpoint \--vpc-subnet-ids subnet-<private-subnet-id-1-vpc-b> subnet-<private-subnet-id-2-vpc-b> \--vpc-security-group-ids sg-<cross-vpc-proxy-sg-id> \--region <region> \--profile <your-profile>aws rds describe-db-proxy-endpoints \--db-proxy-name my-rds-proxy \--region <region> --profile <your-profile>
Note: Replace sg-<cross-vpc-proxy-sg-id> in the above command with the actual security group ID returned from the above create security group command.
Step 3: Configure Network Load Balancer(NLB) in VPC B
Create Security Group for NLB and add inbound rules
Bash
aws ec2 create-security-group \--group-name nlb-sg \--description "Security group for NLB in VPC B" \--vpc-id <vpc-id-vpc-b> \--region <region> \--profile <your-profile>aws ec2 authorize-security-group-ingress \--group-id sg-<nlb-sg-id> \--protocol all \--source-group sg-<nlb-sg-id> \--region <region> \--profile <your-profile>
Update Cross-VPC Endpoint Security Group in VPC B
Update the inbound rule to allow traffic from the NLB security group to allow traffic from the NLB to reach the RDS Proxy endpoint.
Bash
aws ec2 authorize-security-group-ingress \--group-id sg-<cross-vpc-proxy-sg-id> \--protocol tcp \--port 5432 \--source-group sg-<nlb-sg-id> \--region <region> --profile <your-profile>
Create Network Load Balancer
Follow these steps to deploy an NLB in a public subnet of VPC B, providing a stable public entry point for external database access (use the same AZs associated with your RDS Proxy cross-VPC endpoint subnets).
Bash
aws elbv2 create-load-balancer \--name my-nlb \--type network \--subnets subnet-<public-subnet-id-1-vpc-b> subnet-<public-subnet-id-2-vpc-b> \--scheme internet-facing \--security-groups sg-<nlb-sg-id> \--region <region> \--profile <your-profile>
Create Target Group
Create a target group for the NLB to route traffic to the RDS Proxy endpoint IPs.
Bash
aws elbv2 create-target-group \--name my-nlb-targets \--protocol TCP \--port 5432 \--vpc-id <vpc-id-vpc-b> \--target-type ip \--region <region> \--profile <your-profile>
Register Targets
Register the RDS Proxy endpoint IPs as targets for the NLB. You need to provide IP addresses for RDS Proxy cross-VPC endpoint. Resolve this endpoint by running commands like nslookup or ping on an EC2 instance deployed in VPC B, note down the IP addresses
Bash
aws elbv2 register-targets \--target-group-arn <target-group-arn> \--targets Id=<proxy-endpoint-ip-1> Id=<proxy-endpoint-ip-2> \--region <region> \--profile <your-profile>
Create Listener
Attach a listener to the NLB to forward traffic to the target group.
Bash
aws elbv2 create-listener \--load-balancer-arn <load-balancer-arn> \--protocol TCP \--port 5432 \--default-actions Type=forward,TargetGroupArn=<target-group-arn> \--region <region> \--profile <your-profile>
Step 4: Configure DNS Resolution (Optional)
Setting up DNS resolution simplifies access for production or testing environments by mapping domain names to your Network Load Balancer (NLB).
Create Public Hosted Zone (for Production)
Bash
aws route53 create-hosted-zone \--name db.yourcompany.com \--caller-reference "public-dns-$(date +%s)" \--region <region> \--profile <your-profile>
Note: After creation of above public hosted zone, add a CNAME record pointing to you NLB’s DNS endpoint (e.g., my-nlb-123456789012.elb.us-east-1.amazonaws.com).
Create Private Hosted Zone (for Testing)
This option creates a private hosted zone which can be shared with other accounts or VPCs to simulate public access testing for your RDS database. Create a private hosted zone and add a CNAME record for testing access to your RDS database from another VPC or AWS account.
Bash
aws route53 create-hosted-zone \--name test.db.yourcompany.com \--hosted-zone-config '{"PrivateZone":true}' \--vpc VPCRegion=<region>,VPCId=<vpc-id-vpc-b> \--caller-reference "test-dns-$(date +%s)" \--region <region> \--profile <your-profile>
Create DNS Records
Create a file named
private-record.json with following contents.JSON
{"Changes":[{"Action":"CREATE","ResourceRecordSet":{"Name":"myrds.db.internal","Type":"CNAME","TTL":300,"ResourceRecords":[{"Value":"my-nlb-123456789012.elb.us-east-1.amazonaws.com"}]}}]}
Apply the DNS record:=
Bash
aws route53 change-resource-record-sets \--hosted-zone-id <hosted-zone-id> \--change-batch file://private-record.json \--region <region> \--profile <your-profile>
Authorize Association from Second Account (for Private Hosted Zone)
Authorization enables cross-account VPC association, necessary for multi-account testing. Authorize the second account’s VPC to associate with the private hosted zone. Obtain Hosted zone ID from Hosted zone details section of private hosted zone db.internal.
Bash
# Authorize association from primary accountaws route53 create-vpc-association-authorization \--hosted-zone-id <hosted-zone-id> \--vpc VPCRegion=<second-account-region>,VPCId=<vpc-id-second-account> \--region <primary-account-region> --profile <your-profile>
Note: Run above command from your primary AWS account to authorize the second AWS account for VPC association with the private hosted zone.
Associate the second account VPC with the private hosted zone.
Bash
# Associate VPC from second accountaws route53 associate-vpc-with-hosted-zone \--hosted-zone-id <hosted-zone-id> \--vpc VPCRegion=<second-account-region>,VPCId=<vpc-id-second-account> \--region <second-account-region> --profile <second-account-profile>
Note: Run this command from the second AWS account. It will complete the association of second AWS account VPC with private hosted zone. Ensure the IAM role or user in the second aws account has the route53:AssociateVPCWithHostedZone permission.
Bash
# Verify associationaws route53 list-hosted-zones-by-vpc \--vpc VPCRegion=<second-account-region>,VPCId=<vpc-id-second-account> \--region <second-account-region> --profile <second-account-profile>
Step 5: Verify Cross-VPC and Public Access
Follow these steps to verify that your configuration allows secure access from both VPC B and external sources.
Test Access from VPC B
Connect to your application server or EC2 instance in VPC B. Use the appropriate database client command based on your database engine
Bash
# For PostgreSQL:psql -h my-cross-vpc-endpoint.endpoint.proxy-abcdefghijkl.us-east-1.rds.amazonaws.com -p 5432 -U proxy_user# For MySQL/MariaDB:mysql -h my-cross-vpc-endpoint.endpoint.proxy-abcdefghijkl.us-east-1.rds.amazonaws.com -P 3306 -u proxy_user -p# For SQL Server:sqlcmd -S my-cross-vpc-endpoint.endpoint.proxy-abcdefghijkl.us-east-1.rds.amazonaws.com,1433 -U proxy_user -P secure_password
Test Access from second AWS Account simulating Public Access
Connect to an EC2 instance or other resource such as AWS CloudShell in the second account. Use the appropriate database client command based on your database engine
Bash
# For PostgreSQL:psql -h myrds.db.internal -p 5432 -U proxy_user# For MySQL/MariaDB:mysql -h myrds.db.internal -P 3306 -u proxy_user -p# For SQL Server:sqlcmd -S myrds.db.internal,1433 -U proxy_user -P secure_password
Monitoring, Optimization and Security
Follow these steps to monitor the performance of your Network Load Balancer (NLB) and RDS Proxy and optimize your setup for security and efficiency. Create a file named
metrics.json with appropriate metric queries and the following commandCloudWatch Metrics
Bash
aws cloudwatch get-metric-data \--metric-data-queries file://metrics.json \--start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%SZ) \--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \--region <region> \--profile <your-profile>
Optimize Performance
- For RDS Proxy, adjust performance settings. Maximum connection pool size, Connection borrow timeout, Idle client connection timeout
- For NLB, configure health check protocol, health check port, health check path, interval, healthy/unhealthy thresholds
Security
- Modify NLB rules to allow access only from trusted IP ranges
- Enable TLS encryption for RDS Proxy
- Enable automatic rotation, set rotation schedule, select rotation function for Secrets.
Cleanup
Execute these commands in reverse order to clean up resources:
Bash
# Delete Route 53 recordsaws route53 delete-hosted-zone --id <hosted-zone-id># Delete NLBaws elbv2 delete-load-balancer --load-balancer-arn <load-balancer-arn># Delete target groupaws elbv2 delete-target-group --target-group-arn <target-group-arn># Delete RDS Proxy endpointaws rds delete-db-proxy-endpoint \--db-proxy-name my-rds-proxy \--endpoint-name my-cross-vpc-endpoint# Delete RDS Proxyaws rds delete-db-proxy --db-proxy-name my-rds-proxy# Delete security groupsaws ec2 delete-security-group --group-id sg-<nlb-sg-id>aws ec2 delete-security-group --group-id sg-<cross-vpc-proxy-sg-id>aws ec2 delete-security-group --group-id sg-<rds-proxy-sg-id># Delete secretaws secretsmanager delete-secret \--secret-id my-rds-proxy-secret \--force-delete-without-recovery
Key Benefits
- Simplified Networking: Eliminates complex VPC peering configurations
- Enhanced Security: Database remains in private subnets with controlled access
- Scalability: Supports multiple client connections and cross-VPC access
- Flexibility: Works with various RDS database engines (PostgreSQL, MySQL, etc.)
Security Considerations
- Store all credentials in AWS Secrets Manager
- Use security groups to restrict access at each layer
- Enable encryption in transit for database connections
- Implement least-privilege IAM policies for RDS Proxy role
- Monitor access patterns using CloudWatch and AWS CloudTrail
Troubleshooting Tips
- Verify security group rules allow traffic between components
- Ensure RDS Proxy has correct IAM role permissions
- Check NLB target health status
- Validate DNS resolution if using Route 53
- Review CloudWatch logs for connection errors
Additional Resources
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article