When a connection-level failure occurs in PostgreSQL that does not match a more specific subcode, the server raises SQLSTATE 08000 with condition name connection_exception. In psql or application logs this typically surfaces as:
ERROR: connection exception
SQLSTATE: 08000
The exact message text varies depending on the driver and context, but the five-character SQLSTATE 08000 is always present and is the canonical identifier for this condition class.
What This Error Means
SQLSTATE codes beginning with 08 belong to the Connection Exception class defined in the SQL standard. PostgreSQL follows this class structure closely. The generic code 08000 is the catch-all for connection problems that do not have a more precise subcode — it acts as the parent condition for the entire class.
After a connection exception is raised, the underlying connection is typically in an undefined or terminated state. Unlike transaction errors (class 40), which leave the session alive in an aborted transaction that can be rolled back, a class 08 error usually means the connection itself has been severed or is unusable. Any subsequent commands on that connection will either fail immediately or produce unpredictable results until the connection is re-established.
In practice, 08000 most often appears when a lower-level network or protocol event occurs and PostgreSQL (or the client driver) cannot classify it more precisely into one of the named subcodes (08001, 08003, 08004, 08006, 08007, 08P01). It can also be raised explicitly by PL/pgSQL code using RAISE SQLSTATE '08000' to signal a custom connection-related error.
Common Causes
Protocol-level communication failure — A TCP connection was interrupted mid-protocol (e.g., during the authentication handshake or startup sequence) and does not cleanly map to
08006(connection failure) or08001(unable to establish connection).Server terminated the connection unexpectedly — The PostgreSQL backend process crashed or was killed (
pg_terminate_backend()) at a moment when the client was mid-request. The client receives an unexpected EOF before a full response packet.Intermediate proxy or load balancer reset — A connection pooler (PgBouncer, pgpool-II) or cloud proxy (e.g., Cloud SQL Auth Proxy, RDS Proxy) drops the connection and the underlying driver surfaces it as a generic
08000rather than mapping it to a specific subcode.Custom application raise — Application code or a stored procedure explicitly raises
SQLSTATE '08000'as a signal (uncommon, but valid).Driver version mismatch — Older JDBC or libpq versions occasionally misclassify other errors into
08000when they cannot parse an unexpected server response.
How to Fix connection_exception
Implement connection retry logic with backoff. Because
08000indicates the connection is gone, the correct first response is always to discard the connection and open a new one. Do not attempt to reuse the failed connection.# Example using psycopg2 with tenacity from tenacity import retry, stop_after_attempt, wait_exponential import psycopg2 @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10)) def execute_query(dsn, sql): conn = psycopg2.connect(dsn) try: with conn.cursor() as cur: cur.execute(sql) conn.commit() finally: conn.close()Use a connection pool with health checks. Pools like PgBouncer or application-level pools (HikariCP, SQLAlchemy) should be configured to validate connections before handing them to callers. In HikariCP, set
connectionTestQueryor enablekeepaliveTime. In SQLAlchemy, usepool_pre_ping=True.engine = create_engine(dsn, pool_pre_ping=True)Check PostgreSQL server logs around the time of the error. The server-side log often contains more detail — including which backend PID was involved and whether the process terminated abnormally.
# Tail the PostgreSQL log (path varies by distro/installation) tail -n 200 /var/log/postgresql/postgresql-16-main.log | grep -E "ERROR|FATAL|connection"Inspect
pg_stat_activityfor terminated backends. If backends are being killed (by OOM killer, idle-in-transaction timeouts, orpg_terminate_backend), you will see processes disappearing. Cross-reference PIDs with OS-level logs.SELECT pid, state, wait_event_type, wait_event, query_start, query FROM pg_stat_activity WHERE state != 'idle' ORDER BY query_start;Review connection pooler configuration. If using PgBouncer or pgpool-II, verify that
server_lifetime,server_idle_timeout, and health-check settings are consistent with your PostgreSQLtcp_keepalives_idleandidle_in_transaction_session_timeoutsettings. Mismatches cause poolers to hand out dead connections.Catch
08xxxas a class, not just08000. In application error handling, catch the entire connection-exception class so you handle all subcodes consistently:import psycopg2 try: cur.execute(sql) except psycopg2.OperationalError as e: # psycopg2 maps all class-08 errors to OperationalError # pgcode attribute holds the 5-char SQLSTATE if e.pgcode and e.pgcode.startswith('08'): reconnect_and_retry() else: raise
Additional Information
SQLSTATE class 08 subcodes in PostgreSQL:
08000(connection_exception — generic),08001(sqlclient_unable_to_establish_sqlconnection),08003(connection_does_not_exist),08004(sqlserver_rejected_establishment_of_sqlconnection),08006(connection_failure),08007(transaction_resolution_unknown),08P01(protocol_violation). If you consistently see08000instead of a more specific code, a driver or middleware layer may be obscuring the true subcode.Driver mapping: In psycopg2, all class-08 errors raise
psycopg2.OperationalError; check the.pgcodeattribute for the exact SQLSTATE. In JDBC (pgjdbc), class-08 errors throwPSQLExceptionwithgetSQLState()returning08xxx. Node.jspgsetserr.codeto the SQLSTATE string.PL/pgSQL: You can catch this in PL/pgSQL using
WHEN connection_exception THEN(which catches08000specifically) orWHEN SQLSTATE '08000' THEN.Idempotency consideration: Before retrying after any class-08 error, determine whether the original statement was committed. Because the connection was lost, you may not know the outcome. SQLSTATE
08007(transaction_resolution_unknown) is the specific code for this ambiguity; if you see08000in a similar context, treat the transaction outcome as unknown and use idempotent operations or a status-check query after reconnecting.
Frequently Asked Questions
Why do I see 08000 instead of a more specific 08xxx code?
The generic 08000 appears when the client driver or the server cannot determine a more precise subcode. This is common with older driver versions, connection poolers that mask server errors, or when the TCP connection is dropped below the PostgreSQL protocol layer before a detailed error packet can be sent.
Is 08000 the same as 08006 (connection_failure)?
No. 08006 specifically means a failure occurred on an established connection (e.g., the backend crashed mid-query). 08000 is the generic parent code and can appear earlier — during handshake, authentication, or at any point where no specific subcode applies. Functionally, the recovery strategy (discard connection and reconnect) is the same for both.
Can I catch connection_exception in a PL/pgSQL EXCEPTION block?
Yes. Use WHEN connection_exception THEN to catch SQLSTATE 08000 specifically, or WHEN SQLSTATE '08xxx' THEN patterns are not valid SQL — you must list each SQLSTATE individually or use the named condition. Note that catching connection errors inside a function body is rarely useful since the connection is usually gone; this is more relevant for 08003 (connection_does_not_exist) in certain cursor contexts.
How do I tell if the transaction was committed when I get this error?
You generally cannot tell from the 08000 error alone. If your application requires exactly-once semantics, use an idempotency key stored in the database and re-query after reconnecting to check whether the prior transaction committed. SQLSTATE 08007 (transaction_resolution_unknown) is PostgreSQL's explicit signal for this ambiguity, but not all scenarios produce that specific code.