Fix: AWS RDS Proxy Not Working — Endpoint, IAM Auth, Connection Pinning, and Lambda VPC
Quick Answer
How to fix AWS RDS Proxy errors — IAM authentication token mismatch, connection pinning blocking reuse, Lambda VPC routing, Secrets Manager rotation, max_connections, read/write splitter, and TLS requirement.
The Error
You connect via the RDS Proxy endpoint and authentication fails:
FATAL: password authentication failed for user "app_user"Or every Lambda invocation opens a fresh DB connection despite the proxy:
[CloudWatch] Connections: 50 → 100 → 150 → 200 → max_connections reachedOr transactions inside a Lambda function break with pinning errors:
psycopg2.errors.OperationalError: FATAL: terminating connection due to administrator commandOr Lambda can’t reach the proxy at all:
psycopg2.OperationalError: could not connect to server: Connection timed outWhy This Happens
RDS Proxy is a managed connection pooler that sits between your app (typically Lambda) and an RDS database. Its purpose: avoid exhausting database connections when many Lambda instances spin up.
Three main pain points:
- IAM authentication is per-connection. Each new connection requires a freshly generated token (15-min lifetime). Caching the token across long-lived clients fails.
- Pinning forces dedicated connections. When you use a feature that requires a persistent session (PREPARE/SET, advisory locks, large objects, temp tables, etc.), RDS Proxy “pins” the connection to your client until you close it. Pinned connections defeat pooling.
- Lambda VPC is required. RDS Proxy lives in a VPC. Lambda functions outside the VPC can’t reach it.
- Secrets Manager rotation can break things. When the secret rotates, in-flight connections continue with the old password until they reconnect. Proxy handles this gracefully but client-side connection pools may not.
Fix 1: Use the Right Endpoint and Auth Method
Get the proxy endpoint from the AWS Console (RDS → Proxies → your-proxy → Proxy endpoints):
proxy-name.proxy-abc123def456.us-east-1.rds.amazonaws.comDon’t connect to the underlying RDS instance — use the proxy endpoint.
For password authentication (no IAM):
import psycopg2
conn = psycopg2.connect(
host="proxy-name.proxy-abc123.us-east-1.rds.amazonaws.com",
port=5432,
dbname="myapp",
user="app_user",
password=os.environ["DB_PASSWORD"],
sslmode="require", # RDS Proxy requires TLS
)For IAM authentication:
import boto3
import psycopg2
rds_client = boto3.client("rds")
token = rds_client.generate_db_auth_token(
DBHostname="proxy-name.proxy-abc123.us-east-1.rds.amazonaws.com",
Port=5432,
DBUsername="app_user",
)
conn = psycopg2.connect(
host="proxy-name.proxy-abc123.us-east-1.rds.amazonaws.com",
port=5432,
dbname="myapp",
user="app_user",
password=token,
sslmode="require",
)The token is valid for 15 minutes. Generate fresh for each new connection — don’t cache across invocations.
For Node:
import { RDS } from "@aws-sdk/client-rds";
import { Signer } from "@aws-sdk/rds-signer";
const signer = new Signer({
hostname: "proxy-name.proxy-abc123.us-east-1.rds.amazonaws.com",
port: 5432,
username: "app_user",
});
const token = await signer.getAuthToken();
const client = new pg.Client({
host: "proxy-name.proxy-abc123.us-east-1.rds.amazonaws.com",
port: 5432,
database: "myapp",
user: "app_user",
password: token,
ssl: { rejectUnauthorized: false }, // Or load AWS RDS CA cert
});Pro Tip: Use IAM auth in production. Passwords in env vars need rotation; IAM tokens self-rotate every call and are tied to your Lambda’s execution role.
Fix 2: Configure the Proxy Authentication
In the proxy config (AWS Console → RDS → Proxies → your-proxy → Authentication):
Secret: arn:aws:secretsmanager:us-east-1:123456:secret:rds-myapp-AbCdEf
IAM authentication: REQUIRED (or DISABLED)
Client TLS: REQUIREDThe Secrets Manager secret stores the actual database credentials. RDS Proxy uses these to connect to the underlying RDS instance — your app connects to the proxy with either:
- The password from the secret (rotated by Secrets Manager).
- An IAM token (no secret needed by the client).
Set IAM authentication: REQUIRED if your clients always use IAM. Then password-based clients are rejected — safer for production.
Common Mistake: Storing different passwords in Secrets Manager vs your environment. The proxy uses the secret; your client uses the env var; they don’t match → authentication fails. Source-of-truth must be one place.
Fix 3: Detect and Eliminate Pinning
Pinning happens automatically for certain operations. Check via CloudWatch:
Namespace: AWS/RDS
Metric: DatabaseConnectionsCurrentlySessionPinnedIf this is > 0, connections are pinned.
What causes pinning (Postgres):
PREPARE/EXECUTEstatements (named prepared statements).- Session variables set with
SET SESSION(transaction-scopedSET LOCALis fine). - Listening on channels (
LISTEN). - Advisory locks (
pg_advisory_lock). - Temporary tables not cleaned at end of transaction.
- Large objects (
lo_*).
What causes pinning (MySQL):
- User variables (
@varset across statements). - Temporary tables.
LOCK TABLES.SEToutside transaction with session scope.- Prepared statements (yes, this is common!).
The fix is to avoid these or scope them to a single transaction:
BEGIN;
SET LOCAL statement_timeout = '5s'; -- LOCAL = transaction-scoped, no pinning
SELECT ...;
COMMIT;For prepared statements in MySQL, consider client-side parameterized queries (the driver’s ? placeholders are usually fine; PREPARE/EXECUTE statements are the issue).
Pro Tip: Pinning isn’t a fatal error — it just means the proxy can’t pool that connection. If your workload has 5 pinned connections but max_connections is 200, you’re still fine. Worry when pinning rate is high and exhausts your pool.
Fix 4: Lambda VPC Configuration
The Lambda function must be in the same VPC (or a peered VPC) as the proxy:
# SAM template:
MyLambda:
Type: AWS::Serverless::Function
Properties:
VpcConfig:
SecurityGroupIds:
- !Ref LambdaSecurityGroup
SubnetIds:
- subnet-abc
- subnet-defThe Lambda security group must allow outbound to the proxy on port 5432 (Postgres) or 3306 (MySQL). The proxy security group must allow inbound from the Lambda security group.
For Lambdas that also need internet access (calling external APIs), put them in private subnets with NAT Gateway. Lambdas in public subnets can’t get a public IP — they’d fail to reach the internet.
# Wrong: lambda in public subnet — no internet
# Right: lambda in private subnet, with NAT Gateway for internetCommon Mistake: Lambda placed in the same security group as RDS without an explicit allow rule. Same-SG isn’t always implicit allow — add an inbound rule on the proxy SG allowing 0.0.0.0/0 from the Lambda SG, port 5432.
For testing connectivity from inside a Lambda:
import socket
sock = socket.create_connection(
("proxy-name.proxy-abc.us-east-1.rds.amazonaws.com", 5432),
timeout=3,
)
sock.close()
print("Reachable")If this times out, you have a networking problem (VPC/SG/route table), not a database problem.
Fix 5: Connection Pool Settings
On the proxy:
MaxConnectionsPercent— what % of the underlying RDS max_connections the proxy can use (default 100).MaxIdleConnectionsPercent— what % of the pool can be idle (default 50).ConnectionBorrowTimeout— how long a client waits for a connection from the pool (default 120s).
For high-traffic workloads:
MaxConnectionsPercent: 90 # Leave some headroom for the RDS instance
MaxIdleConnectionsPercent: 50 # Keep many idle for fast burst response
ConnectionBorrowTimeout: 60 # Fail faster than the defaultOn the client side, each Lambda invocation creates a connection but the proxy multiplexes them. Don’t create your own connection pool in Lambda — it defeats the purpose of the proxy.
# WRONG: client-side pool of size 10 on each Lambda instance
from psycopg2.pool import ThreadedConnectionPool
pool = ThreadedConnectionPool(2, 10, ...) # Bad
# RIGHT: single connection per invocation, proxy pools across invocations
conn = psycopg2.connect(...)
try:
# use conn
conn.commit()
finally:
conn.close()Pro Tip: For Lambda with the proxy, prefer one connection per invocation. The proxy handles pooling across Lambda instances; client-side pools just complicate things.
Fix 6: Secrets Manager Rotation
When Secrets Manager rotates the underlying password, the proxy seamlessly switches. Your IAM-authenticated clients are unaffected. Password-authenticated clients with the old password from the secret will fail next time they reconnect — that’s expected during rotation.
To handle rotation gracefully:
# Always read the secret fresh:
import boto3
import json
def get_db_password():
sm = boto3.client("secretsmanager")
response = sm.get_secret_value(SecretId="rds-myapp")
return json.loads(response["SecretString"])["password"]
def connect():
return psycopg2.connect(
host="proxy-name...",
password=get_db_password(),
...
)For long-running services (not Lambda), cache the secret for a short time (1-5 minutes) and retry connection on auth failure:
@lru_cache(maxsize=1)
def get_secret_cached():
return get_db_password()
def connect_with_retry():
try:
return psycopg2.connect(password=get_secret_cached(), ...)
except psycopg2.OperationalError as e:
if "authentication failed" in str(e):
get_secret_cached.cache_clear()
return psycopg2.connect(password=get_secret_cached(), ...)
raiseCommon Mistake: Hardcoding the password in env vars. Then rotation breaks your app until you redeploy. Always read from Secrets Manager (or use IAM auth).
Fix 7: Read/Write Endpoints
For Aurora with read replicas, RDS Proxy supports read/write splitting:
- Writer endpoint:
proxy-name.proxy-abc.us-east-1.rds.amazonaws.com— connects to the writer. - Reader endpoint:
proxy-name-ro-abc.proxy-abc.us-east-1.rds.amazonaws.com— connects to a reader.
Your app routes:
# Writes:
write_conn = psycopg2.connect(host="proxy-name...", ...)
# Reads:
read_conn = psycopg2.connect(host="proxy-name-ro-abc...", ...)The proxy load-balances reads across the read replicas. Writes go to the writer.
Common Mistake: Using only the writer endpoint for everything. You’re not using the read replicas — wasted capacity. Route SELECTs to the reader endpoint.
Beware: replica lag means reads against a reader endpoint can return stale data. For “read your own writes,” route reads to the writer (or use Aurora’s aurora_replica_read_consistency setting).
Fix 8: Monitoring and Cost
Key CloudWatch metrics:
DatabaseConnectionsCurrentlyBorrowed— pool connections in use.DatabaseConnectionsCurrentlyInTransaction— actively running a transaction.DatabaseConnectionsCurrentlySessionPinned— pinning rate.ClientConnections— total client connections to the proxy.QueryDatabaseResponseLatency— proxy-to-DB latency.
Set alarms on:
- High pinning rate (your client code is creating pinning operations).
ClientConnectionsnear max — proxy can’t keep up.- Low
DatabaseConnectionsCurrentlyBorrowed— proxy not multiplexing well (could mean pinning).
RDS Proxy is billed per vCPU of the underlying RDS instance per hour. For db.t3.medium (2 vCPU) at ~$0.015/vCPU/hour, that’s roughly $22/month for the proxy alone. Bigger instances cost more.
Pro Tip: RDS Proxy makes sense for Lambda-heavy workloads. For containerized apps with long-lived processes (ECS, EKS), the runtime’s own connection pool may suffice — RDS Proxy’s overhead isn’t worth it.
Still Not Working?
A few less-obvious failures:
Connection refusedimmediately. Security group blocks the port. Check Lambda SG outbound + proxy SG inbound.SSL connection has been closed unexpectedly. Proxy requires TLS but client isn’t using it. Setsslmode=require(psycopg2) or equivalent.- Slow queries via proxy but fast direct. Proxy adds ~1-3ms per query. For very-low-latency reads, sometimes worth bypassing the proxy. But high-volume Lambdas benefit more from pooling.
Too many connectionson RDS even with proxy. YourMaxConnectionsPercentis set too low, or pinning is consuming your pool. Increase the cap or fix pinning.- Lambda timing out on connection. ConnectionBorrowTimeout is high and the proxy is exhausted. Reduce timeout to fail fast, then scale the proxy.
- Aurora Serverless v2 + Proxy weirdness. Aurora Serverless v2 has its own scaling layer. Combining with RDS Proxy is supported but can produce confusing metrics. For serverless workloads, sometimes Aurora’s Data API is simpler.
Authentication token expired. IAM token is older than 15 minutes. Don’t cache tokens; regenerate per connection.pg_hba.confdoesn’t allow connection. Check the RDS instance parameter group — IAM-required users need specific entries. AWS manages this for default cases.
For related AWS database connectivity issues, see AWS RDS connection timed out, Postgres connection refused, AWS Lambda timeout, and AWS IAM permission denied.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS RDS Connection Timed Out from Lambda or EC2
How to fix AWS RDS connection timeout errors from Lambda functions and EC2 instances — security group configuration, VPC settings, connection pooling, and RDS Proxy setup for Lambda.
Fix: AWS Lambda SnapStart Not Working — Version vs Alias, Restore Hooks, and Uniqueness Bugs
How to fix Lambda SnapStart errors — feature requires published version, $LATEST not supported, restore hook for stale connections, UUID collisions after snapshot, time-based state staleness, and pricing surprises.
Fix: AWS Step Functions Not Working — ASL Syntax, Map State, Error Handling, and IAM
How to fix AWS Step Functions errors — Amazon States Language syntax, Standard vs Express workflows, Distributed Map for large datasets, Retry/Catch error handling, Lambda invoke optimization, and IAM execution role permissions.
Fix: AWS Lambda Layer Not Working — Module Not Found or Layer Not Applied
How to fix AWS Lambda Layer issues — directory structure, runtime compatibility, layer ARN configuration, dependency conflicts, size limits, and container image alternatives.