AWS RDS PostgreSQL database

AWS RDS connection limit exceeded: too many connections

Fix RDS PostgreSQL connection limit errors by identifying idle connections, terminating leaked sessions, and implementing connection pooling with RDS Proxy or PgBouncer.

·
Your application suddenly starts rejecting database connections: "FATAL: too many connections". New requests fail, existing sessions hang, and the problem escalates every second. This runbook shows how to regain access and prevent it from recurring.

This runbook covers RDS PostgreSQL connection exhaustion. For ongoing database management and monitoring, see CloudOps Maintenance. For architecture guidance on connection pooling patterns, book a consulting session.

Symptoms

Application logs show connection failures. Users experience timeouts or 500 errors:

# Application error logs show:
# FATAL: too many connections for role "app_user"
# FATAL: remaining connection slots are reserved for non-replication superuser connections

# Check current connection count from a superuser session
# (if you can still connect via psql as superuser)
psql -h my-rds-instance.abcdefg.eu-west-1.rds.amazonaws.com \
  -U master_user -d mydb -c "SELECT count(*) FROM pg_stat_activity;"

# Check max_connections setting
psql -h my-rds-instance.abcdefg.eu-west-1.rds.amazonaws.com \
  -U master_user -d mydb -c "SHOW max_connections;"

# Check via CloudWatch if you can't connect at all
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions Name=DBInstanceIdentifier,Value=my-rds-instance \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 60 --statistics Maximum \
  --query 'Datapoints | sort_by(@, &Timestamp) | [-5:].[Timestamp,Maximum]'

The connection count is at or near max_connections. No new connections can be established.

Cause

RDS PostgreSQL has a fixed max_connections limit determined by instance class (e.g., db.t3.micro = 87, db.t3.medium = 150, db.r5.large = 1600). Connections are exhausted when:

  • Connection leaks: Application code opens connections but does not close them on error paths or timeouts. Over time, leaked connections accumulate.
  • Missing connection pooling: Each application instance opens its own connection pool. With autoscaling, new instances bring new pools, quickly exhausting the limit.
  • Idle transactions: Long-running queries or abandoned transactions hold connections open indefinitely.
  • Burst traffic: A sudden spike in requests creates more connections than the database can handle, and once saturated, the backpressure causes cascading failures.

The default max_connections formula for RDS is LEAST({DBInstanceClassMemory/9531392}, 5000), which is often lower than teams expect for smaller instance classes.

Fix

A) Immediate relief: terminate idle connections:

# Connect as superuser (RDS master user)
psql -h my-rds-instance.abcdefg.eu-west-1.rds.amazonaws.com \
  -U master_user -d mydb

# Check connection breakdown by state and application
SELECT state, application_name, count(*)
FROM pg_stat_activity
GROUP BY state, application_name
ORDER BY count DESC;

# Identify idle connections older than 5 minutes
SELECT pid, usename, application_name, state,
       now() - state_change AS idle_duration
FROM pg_stat_activity
WHERE state = 'idle'
  AND now() - state_change > interval '5 minutes'
ORDER BY idle_duration DESC;

# Terminate idle connections (careful: this kills sessions)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
  AND now() - state_change > interval '5 minutes'
  AND usename != 'rdsadmin';

# Verify connections freed
SELECT count(*) FROM pg_stat_activity;

B) Implement connection pooling with RDS Proxy:

# Create RDS Proxy (via AWS CLI)
aws rds create-db-proxy \
  --db-proxy-name my-service-proxy \
  --engine-family POSTGRESQL \
  --auth Description="proxy auth",AuthScheme=SECRETS,SecretArn=arn:aws:secretsmanager:eu-west-1:123456789012:secret:rds-creds,IAMAuth=DISABLED \
  --role-arn arn:aws:iam::123456789012:role/rds-proxy-role \
  --vpc-subnet-ids subnet-abc123 subnet-def456 \
  --vpc-security-group-ids sg-12345678

# Register target (your RDS instance)
aws rds register-db-proxy-targets \
  --db-proxy-name my-service-proxy \
  --db-instance-identifiers my-rds-instance

# Update application connection string to point to the proxy endpoint
# Proxy endpoint: my-service-proxy.proxy-abcdefg.eu-west-1.rds.amazonaws.com

C) Adjust max_connections parameter (short-term fix):

# Check current parameter group
aws rds describe-db-instances \
  --db-instance-identifier my-rds-instance \
  --query 'DBInstances[0].DBParameterGroups[0].DBParameterGroupName'

# Modify max_connections (requires custom parameter group, not default)
aws rds modify-db-parameter-group \
  --db-parameter-group-name my-custom-pg \
  --parameters "ParameterName=max_connections,ParameterValue=300,ApplyMethod=pending-reboot"

# Reboot to apply (causes brief downtime)
aws rds reboot-db-instance --db-instance-identifier my-rds-instance

Prevention: Always use connection pooling in production (RDS Proxy for managed AWS, PgBouncer for self-managed). Set idle_in_transaction_session_timeout to kill abandoned transactions automatically. Configure CloudWatch alarms on DatabaseConnections at 80% of max_connections to catch issues before they become outages.

Verification

# 1. Confirm connections are below limit
psql -h my-rds-instance.abcdefg.eu-west-1.rds.amazonaws.com \
  -U master_user -d mydb -c \
  "SELECT count(*) as current, current_setting('max_connections')::int as max FROM pg_stat_activity;"
# Expected: current is well below max

# 2. Verify application can connect
curl -s https://my-app.example.com/health | jq '.database'
# Expected: "connected" or equivalent health check pass

# 3. Check no connection leak pattern in CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions Name=DBInstanceIdentifier,Value=my-rds-instance \
  --start-time $(date -u -d '30 minutes ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 --statistics Average
# Expected: stable connection count, not continuously rising

# 4. If using RDS Proxy, verify it's handling connections
aws rds describe-db-proxy-targets \
  --db-proxy-name my-service-proxy \
  --query 'Targets[0].TargetHealth'
# Expected: State = "AVAILABLE"

If the application serves requests without connection errors and the CloudWatch metric stays stable, the issue is resolved. Monitor for 24 hours to confirm no slow leak pattern reappears.

Maxed-out database connections mean new requests are rejected immediately. For production databases, this is an outage that escalates every second: existing users start timing out, background jobs pile up, and the connection queue creates a thundering herd when capacity is finally restored. Without connection pooling and alerting, teams often discover this problem only when customers report it.

 

Jerzy Kopaczewski

Database connections maxing out?

Book a free 30-minute call. We'll review your RDS configuration and implement connection pooling to prevent future outages.