transaction_timeout
transaction_timeout (NEW in PostgreSQL 17) sets the maximum total duration of any single transaction in a session — measured from the start of the transaction…
Tier 1 — Lab-Proven — Measured behavior and safety boundaries are available below.
Measured Effect
summary
MEASURED the total-transaction cap firing and the session being TERMINATED (FATAL). A transaction ran several short statements (pg_sleep(0.4)) separated by short client gaps — each statement+gap well under the cap, but the running TOTAL crossing it. Wall-clock was measured around the call so the give-up point is visible.
observations
| transaction timeout | scenario | outcome | elapsed s | note |
|---|---|---|---|---|
| 2s | multi-statement txn, total > 2s | FATAL — connection terminated | 2.4 | smoking gun: terminating connection due to transaction timeout mid-transaction; the end-of-txn marker never prints |
| 10s | same txn, total ~2s | COMMIT — marker prints | 3.2 | under a generous cap the whole transaction completes normally |
| 1s | BEGIN; pg_sleep(3) | FATAL — session terminated | 1.x | no follow-up statement in the same session is possible — the connection is GONE |
session survives
It does NOT — unlike statement_timeout (#26) and lock_timeout (#27) which raise a non-fatal cancel and keep the connection, transaction_timeout raises a FATAL: the backend is terminated, the connection is closed, and the transaction rolls back. The server log shows FATAL: terminating connection due to transaction timeout (captured for every firing).
reading
It is a hard cap on a whole transaction's wall-clock duration, not a performance dial. Once the running total of a transaction passes the limit, the server terminates the connection (FATAL) and rolls the transaction back; the elapsed at termination ≈ the cap (here ~2.4s for a 2s setting, the small extra being client/exec overhead). It catches what statement_timeout and idle_in_transaction_session_timeout miss because it sums BOTH active and idle time inside the transaction.
honesty note
transaction_timeout is a server-side timer; the chosen seconds are lab values, the mechanism (terminate-on-total-exceeded, FATAL, connection closed, txn rolled back) is deterministic and box-independent. Measured elapsed includes a few hundred ms of docker-exec/client overhead, which is why a 2s setting shows ~2.4s. It does not apply to prepared (two-phase) transactions.
Reproduced Danger
Sizing / Safe Range
Workload Guidance
Unlock Incident Pro
The free sections tell you what to do. Pro is the measured proof — the exact failure signature, safe-ceiling formula, and per-workload defaults, each captured on real PG 14–18 runs.
- Reproduced Danger
- Sizing / Safe Range
- Workload Guidance
transaction_timeout (NEW in PostgreSQL 17) sets the maximum total duration of any single transaction in a session — measured from the start of the transaction (or of a single-statement transaction) until it commits or rolls back. It bounds the WHOLE transaction: time spent running statements AND time spent idle between statements inside the transaction. If the limit is exceeded, the backend is terminated with a FATAL error — FATAL: terminating connection due to transaction timeout — which CLOSES the connection and rolls back the transaction (it is the heavier hammer; statement_timeout and lock_timeout only cancel a statement and keep the session alive). The default is 0, meaning unlimited. It fills the gap left by its siblings: statement_timeout caps a SINGLE statement's run time (so a transaction made of many short statements escapes it), and idle_in_transaction_session_timeout caps only IDLE time inside a transaction (so a continuously-active long transaction escapes it) — transaction_timeout catches both because it caps the total. It does NOT apply to prepared (two-phase) transactions. context=user, so it can be set per session, role, database, or cluster-wide and takes effect immediately. Because it does not exist before PG 17, any safety relying on it is absent on PG 14-16.
- name
transaction_timeout
- setting default
0 (disabled — unlimited)
- unit
ms
- vartype
integer
- category
Client Connection Defaults / Statement Behavior
- context
user
- min val
0
- max val
2147483647
- enumvals
(n/a — integer ms)
- boot val
0
- reset val
0
- pending restart
false
- short desc
Sets the maximum allowed duration of any transaction within a session (not a prepared transaction).
- ground truth note
Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-20. The parameter is ABSENT on PG 14, 15, 16 (introduced_in=pg17) and PRESENT on PG 17 and 18 with setting=0, boot_val=0, reset_val=0 (default_changed=false between 17 and 18). context=user (session-changeable, no reload/restart). vartype=integer, unit=ms, min_val=0 (0 disables), max_val=2147483647. category='Client Connection Defaults / Statement Behavior', short_desc='Sets the maximum allowed duration of any transaction within a session (not a prepared transaction).'
VERSION CLIFF: transaction_timeout is NEW in PostgreSQL 17 — it does not exist on PG 14, 15, 16 (setting it there raises ERROR: unrecognized configuration parameter). On PG 17 and 18 it is identical: default=0 (disabled), min=0, max=2147483647, context=user, vartype=integer, unit=ms (default_changed=false 17->18). Its timeout-family siblings statement_timeout, lock_timeout and idle_in_transaction_session_timeout exist and are unchanged across 14-18; transaction_timeout is the one with the cliff. Per-version detail in _guc_matrix_machine.
context=user: set it per session with SET transaction_timeout = '30s'; (effective immediately), or SET LOCAL transaction_timeout = '...' inside a transaction, or per role/database with ALTER ROLE/DATABASE . SET, or cluster-wide via postgresql.conf / ALTER SYSTEM + reload. CONTEXT PROOF (captured literal, identical on and): context=user, so a session SET is accepted with no wrong-context error. BOUNDARY PROOF (captured literal, identical on and): a negative value is rejected at set time -- ALTER SYSTEM SET transaction_timeout = -1; -> ERROR: -1 ms is outside the valid range for parameter "transaction_timeout" (0 ms .. 2147483647 ms). The valid range is 0 . 2147483647 ms; 0 DISABLES the cap (unlimited, the default). The value is in milliseconds but accepts unit suffixes like '30s', '500ms', '5min'. VERSION PROOF (captured literal): on SET transaction_timeout='2s'; -> ERROR: unrecognized configuration parameter "transaction_timeout" — the knob simply does not exist before PG 17.
On managed providers that run PostgreSQL 17+ (Azure Database for PostgreSQL Flexible Server, Amazon RDS / Aurora, Cloud SQL, Crunchy, Supabase, Neon, Heroku), transaction_timeout is exposed as a server parameter, default 0, and because context=user you can set it per-session/role/database and via SET LOCAL without provider involvement. On clusters still on 14-16 the parameter is unavailable — do not rely on it there; use statement_timeout + idle_in_transaction_session_timeout + lock_timeout instead. Recommended posture on 17+: a modest transaction_timeout as a backstop for short OLTP/interactive roles, generous or 0 for batch/ETL/migration roles, gated on server_version if the same config targets mixed-version fleets, and paired with statement_timeout (per-statement) and idle_in_transaction_session_timeout (idle) for complete coverage.
Common Pitfalls
- Assuming transaction_timeout exists on PG 14-16 — it is NEW in PG 17; setting it on older servers raises
unrecognized configuration parameterand the intended protection is absent. - Setting it below the duration of a legitimate batch/ETL transaction — it will terminate the connection and roll back real work.
- Forgetting it is FATAL (session terminated), not a per-statement cancel like statement_timeout/lock_timeout — clients are disconnected and must reconnect.
- Expecting statement_timeout or idle_in_transaction_session_timeout alone to catch a long multi-statement transaction — they don't; only transaction_timeout caps the total.
- Setting it tighter than statement_timeout by accident, so transaction_timeout fires first (FATAL) instead of statement_timeout (non-fatal) for a long single statement.
- Expecting it to bound prepared (two-phase) transactions — it explicitly does not.
Evidence and References
- PostgreSQL 17 docs: Runtime Config — Client Connection Defaults / Statement Behavior (transaction_timeout — terminate any session whose transaction exceeds the limit; 0 disables; does not apply to prepared transactions; NEW in PG 17).
- PostgreSQL 17 release notes: addition of transaction_timeout (bounds total transaction duration, complementing statement_timeout and idle_in_transaction_session_timeout).
- PostgreSQL docs: statement_timeout, lock_timeout, idle_in_transaction_session_timeout, idle_session_timeout (the rest of the timeout family — per-statement run time, lock wait, in-transaction idle, and out-of-transaction idle respectively).
- PostgreSQL docs: pg_stat_activity and server log —
terminating connection due to transaction timeoutis the FATAL emitted when the cap is hit. - Lab capture 2026-06-20: — a multi-statement transaction whose total crossed a 2s cap was terminated (FATAL) at ~2.4s while the same txn under 10s committed ; the version cliff (
unrecognized configuration parametervs accepted) and the coverage gap (same ~2s txn escapes statement_timeout=1s and idle_in_transaction_session_timeout=1s but is terminated by transaction_timeout=1.5s) ; boundary min=0 disables, range error on -1, context=user, identical /; pg_settings ground truth PG 14-18 (-18) confirming absent 14-16, present 17-18.
Technical Reference
- Category
- Client Connection Defaults / Statement Behavior
- Type
- integer (ms (milliseconds; 0 disables))
- Blast radius
- session
- Severity
- Moderate
- Context
- user
- Versions
- 17, 18 (introduced in 17; absent on 14, 15, 16)
- Reviewed
- 2026-06-20
Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.