psycopg2.OperationalError: server closed the connection unexpectedly

Python · updated 2026-11-02

Raised by psycopg (and wrapped by SQLAlchemy as sqlalchemy.exc.OperationalError) on the first query sent over a connection the server or a proxy already closed. It usually hits the first request after a quiet period, after a database restart or failover, or behind PgBouncer, a cloud proxy or a load balancer with an idle timeout.

sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
[SQL: SELECT orders.id FROM orders WHERE orders.customer_id = %(customer_id_1)s]

Quick fix

Let the pool test connections before using them and replace stale ones. In SQLAlchemy set pool_pre_ping=True and pool_recycle below the shortest idle timeout between your app and the database. Retry the query once for read-only work when the error comes from a dropped connection.

  1. Note when it happens: first request after idle time, after a deploy or restart, or under load.
  2. Turn on pool_pre_ping=True so dead connections are replaced before a query uses them.
  3. Set pool_recycle (seconds) below the idle timeout of PgBouncer, the cloud proxy or the load balancer.
  4. Check the Postgres log for the same time: a restart, failover, OOM kill or idle_session_timeout explains it.
  5. For long jobs, open a fresh session per unit of work instead of holding one connection for hours.
python
from sqlalchemy import create_engine

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,   # test each connection on checkout, reconnect if it is dead
    pool_recycle=280,     # seconds; below the shortest idle timeout on the path to Postgres
)

Why it happens

Idle connections cut by a proxy or load balancer

PgBouncer, cloud SQL proxies and TCP load balancers close connections that sit idle past their timeout. The pool still thinks they are open.

The database restarted or failed over

Every pooled connection from before the restart is dead, and each one fails once on first use.

Server-side timeouts

idle_session_timeout or idle_in_transaction_session_timeout on the Postgres side end sessions the app still holds.

The backend was killed

A query that ran out of memory or was cancelled by an admin can take its connection down with it; check the Postgres log.

Other fixes

  • Keep transactions short so connections are not held idle in the middle of one.
  • Monitor pool checkouts and reconnects so a failover shows up as a spike, not a mystery.
  • For psycopg 3 without SQLAlchemy, use psycopg_pool with a check callback (ConnectionPool.check_connection).

Same error, other wording

  • psycopg.OperationalError: consuming input failed: server closed the connection unexpectedly
  • sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) SSL SYSCALL error: EOF detected
  • psycopg2.InterfaceError: connection already closed

Let your coding agent catch the next one

Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:

> Find the "server closed the connection unexpectedly" errors in production, line them up with deploys, restarts and idle periods, and set up the connection pool (pre-ping and recycle) so stale connections are replaced before use.

Plans from $29/moSee what your agent readsSee the setupCancel any time from the billing portal

Related errors

References: SQLAlchemy: Dealing with disconnects (pool_pre_ping), PostgreSQL: Client connection defaults (idle_session_timeout)