MySQL

How to Fix MySQL Connection Pool Timeout on AWS RDS Proxy Idle Instances

4 min read by DebuggedIt

Quick answer

Your application's first request after a period of low traffic fails with a connection timeout when going through RDS Proxy, even though the underlying RDS...

Your application's first request after a period of low traffic fails with a connection timeout when going through RDS Proxy, even though the underlying RDS database itself is healthy and reachable. RDS Proxy manages its own connection pool separately from your application's pool, and idle connection handling between these two layers can interact in ways that produce exactly this kind of intermittent timeout.

The Problem

A request that follows a quiet period fails with a timeout, while subsequent requests immediately after succeed normally:

Error: Connection timeout: failed to acquire connection from pool within 10000ms
    at Pool.query (/app/node_modules/mysql2/promise.js:...)

Checking RDS Proxy's own CloudWatch metrics around the same timestamp often shows a connection churn event β€” connections being closed and re-established β€” correlating with the timeout:

aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnectionsCurrentlyBorrowed \
  --dimensions Name=ProxyName,Value=my-proxy \
  --start-time 2026-08-07T10:00:00Z --end-time 2026-08-07T10:30:00Z \
  --period 60 --statistics Average

Why It Happens

RDS Proxy sits between your application and the actual RDS instance, maintaining its own pool of connections to the database and multiplexing your application's connections onto that pool. Idle connection handling happens at multiple independent layers, and a mismatch between them can cause exactly this symptom:

  • RDS Proxy's IdleClientTimeout closes client-side connections (from your application to the proxy) that have been idle too long, and if your application's own connection pool doesn't detect and handle this closure gracefully, the next request using that stale pooled connection fails.
  • The underlying RDS instance's own wait_timeout closing connections from RDS Proxy to the actual database after a period of inactivity, independent of RDS Proxy's client-facing timeout β€” a mismatch between these two layers' timeout values can leave RDS Proxy holding a connection it believes is valid but that the database has actually already closed.
  • Connection pool warming behavior β€” after a genuinely quiet period, RDS Proxy may need to establish fresh connections to the database, and if your application's pool immediately demands more connections than RDS Proxy can establish within your application's configured timeout window, requests time out waiting.
  • Application-side connection pool not validating connections before use, attempting to use a connection that's already been silently closed by one of the timeout layers above.

The Fix

Align the timeout values across every layer so they're consistent rather than working against each other. Check RDS Proxy's current idle client timeout:

aws rds describe-db-proxies --db-proxy-name my-proxy --query 'DBProxies[0].IdleClientTimeout'

Ensure the underlying RDS instance's wait_timeout is set to a value at least as generous as RDS Proxy's idle timeout, so RDS Proxy's connections to the actual database aren't closed prematurely relative to what the proxy itself expects:

SHOW VARIABLES LIKE 'wait_timeout';

If needed, adjust via a custom parameter group associated with the RDS instance:

aws rds modify-db-parameter-group \
  --db-parameter-group-name my-mysql-params \
  --parameters "ParameterName=wait_timeout,ParameterValue=1800,ApplyMethod=immediate"

Configure your application's own connection pool to validate connections before handing them out ("test on borrow"), so a connection that's already been silently closed at any layer gets detected and replaced rather than causing a failed query:

const pool = mysql.createPool({
  host: 'my-proxy.proxy-xxxxxx.us-east-1.rds.amazonaws.com',
  user: 'app_user',
  password: process.env.DB_PASSWORD,
  waitForConnections: true,
  connectionLimit: 10,
  enableKeepAlive: true,
  keepAliveInitialDelay: 10000,
});

For pools that support it, enable a periodic keepalive query to prevent connections from ever going idle long enough to hit any layer's timeout in the first place, trading a small amount of overhead for more predictable connection availability:

setInterval(async () => {
  try {
    await pool.query('SELECT 1');
  } catch (err) {
    console.error('keepalive failed', err);
  }
}, 60000);

Also consider RDS Proxy's own MaxConnectionsPercent and MaxIdleConnectionsPercent settings, which control how many connections it maintains toward the underlying database and how many it keeps warm even during idle periods β€” raising the idle percentage slightly can reduce the frequency of cold connection establishment after quiet periods:

aws rds modify-db-proxy-target-group \
  --db-proxy-name my-proxy \
  --target-group-name default \
  --connection-pool-config "MaxIdleConnectionsPercent=25"

Still Not Working?

If timeouts persist specifically after longer idle periods despite tuning the above, check RDS Proxy's CloudWatch metrics for connection establishment latency during those specific windows, which reveals whether the timeout is genuinely about connection pool exhaustion versus RDS Proxy needing measurable time to establish fresh connections to a database that scaled down or went idle:

aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnectionsCurrentlyBorrowed \
  --dimensions Name=ProxyName,Value=my-proxy \
  --start-time 2026-08-07T00:00:00Z --end-time 2026-08-07T12:00:00Z \
  --period 60 --statistics Maximum

If Aurora Serverless v2 is the underlying database and it's scaling capacity down during idle periods, factor that scaling latency into your application's own connection timeout settings, giving requests after a quiet period enough time to wait for the database to scale back up rather than failing prematurely on a timeout tuned only for already-warm capacity.