How to Fix a Postgres Connection Pool Leak

All 10 connections are in use, nothing is running a query, and every new request hangs forever - that's a connection pool leak. Connections checked out but never returned pile up silently until the pool is dry. Here's how to find and fix it.

DevOps Engineerpythonpostgresconnection-pool

What a connection pool leak looks like

Your API responds normally for a few minutes, then all requests hang indefinitely - no errors, no timeouts from the app, just silence. Restarting the process fixes it for a few minutes, then the same thing happens again.

The giveaway is a /status endpoint (or a direct pg_stat_activity query) that shows all connections in use even when no requests are actively running:

curl http://localhost:8080/status
# {"connections_used": 10, "connections_max": 10}

# Fire some requests, especially ones that hit error paths
for i in $(seq 1 10); do curl -s -o /dev/null http://localhost:8080/products; done
curl http://localhost:8080/status
# {"connections_used": 10, "connections_max": 10}  <- never goes down

The root cause: release_conn missing from the error path

The typical raw psycopg2 pool pattern looks like this:

from psycopg2 import pool

db_pool = pool.SimpleConnectionPool(1, 10, dsn="...")

def get_conn():
    return db_pool.getconn()

def release_conn(conn):
    db_pool.putconn(conn)

@app.route("/products")
def list_products():
    conn = get_conn()
    try:
        cur = conn.cursor()
        cur.execute("SELECT id, name FROM products LIMIT 50")
        rows = cur.fetchall()
        cur.close()
        release_conn(conn)        # called on the happy path
        return jsonify(rows)
    except Exception as e:
        return jsonify({"error": str(e)}), 500   # BUG: release_conn never called

When an exception fires before release_conn, the connection is checked out but never returned. After 10 such errors the pool is dry and every call to getconn() blocks, taking all healthy requests down with it.

The fix: move release_conn into a finally block

finally runs on every exit from the try - success, exception, or early return:

@app.route("/products")
def list_products():
    conn = get_conn()
    try:
        cur = conn.cursor()
        cur.execute("SELECT id, name FROM products LIMIT 50")
        rows = cur.fetchall()
        cur.close()
        return jsonify(rows)
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        release_conn(conn)    # runs on every exit path

Every handler that calls get_conn() needs the same pattern - including 404 early returns and any code path that raises before the happy-path putconn.

The better pattern: context managers

Raw getconn/putconn is error-prone because the programmer has to remember to release on every exit. Modern psycopg2 and psycopg3 support context managers that handle this automatically:

# psycopg3 - connection is returned to the pool on __exit__
with pool.connection() as conn:
    cur = conn.execute("SELECT id, name FROM products LIMIT 50")
    rows = cur.fetchall()
    return jsonify(rows)

ORMs (SQLAlchemy, Django ORM) also manage connection lifecycle for you. Raw pool APIs are footgun-shaped - context managers are the right long-term fix.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

Why do all my Postgres requests hang after a few minutes?

The most common cause is a connection pool leak - connections are checked out but never returned to the pool. Once the pool is exhausted (e.g. all 10 slots), new requests block on getconn() indefinitely. Check your /status endpoint or pg_stat_activity to confirm all connections are in use.

How do I fix a Postgres connection pool leak in Python?

Move your release_conn (or putconn) call into a finally block so it runs on every exit path - including exceptions and early returns. Even better, use a context manager (with pool.connection()) so the language handles cleanup automatically.

How do I detect a connection pool leak?

Watch the pool's 'connections_used' count after requests, especially error requests. If it climbs and never drops back, connections are leaking. You can also query pg_stat_activity on the database and count idle connections belonging to your app.

Keep learning

Fix the Nginx 502 Bad GatewayDevOps projectRecover a Crashed Linux ServiceDevOps projectCorrect a Postgres Port MismatchDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →