statement_timeout
Cancels any statement exceeding the time limit (ms). Default 0 = no limit. Too aggressive kills legitimate analytics and VACUUM.
Tier 1 — Lab-Proven — Measured behavior and safety boundaries are available below.
What We Observed
box
(PG 17.10)
mechanism
statement_timeout cancels the current statement with ERROR if it exceeds the timeout. Non-fatal — the connection stays open.
live verification
- 1000ms timeout 2s sleep
CANCELED (confirmed 2026-06-24)
- 5000ms timeout 2s sleep
completed (confirmed 2026-06-24)
- 30000ms timeout 2s sleep
completed (confirmed 2026-06-24)
from st effect
statement_timeout cancels a single statement that exceeds the limit (non-fatal ERROR); elapsed proves it cancels AT the timeout, not after the statement would have finished.
timeout=5s sleep 2s (< 5s) | completed | elapsed=2.4s timeout=2s sleep 10s (> 2s) | CANCELED (statement timeout) | elapsed=2.7s timeout=0 sleep 3s (timeout off) | completed | elapsed=3.3s
Real CPU-bound query (md5 over 20M rows), timeout off vs on:
timeout=0 20M md5 (off) | completed | elapsed=17.3s timeout=1s 20M md5 (1s cap) | CANCELED (statement timeout) | elapsed=1.5s
Connection survives the cancel? (cancel a statement, then run SELECT 1 in the SAME session):
ERROR: canceling statement due to statement timeout
session still alive: 1
(A non-fatal ERROR cancels just the statement; the session keeps working -- contrast with idle_in_transaction_session_timeout, which is FATAL and terminates the whole connection.)
from st danger
=== EDGE 1: too LOW cancels legitimate long work === A real nightly-style aggregate (md5 over 20M rows). Adequate cap vs over-aggressive cap:
legit report (60s cap) timeout=60s | completed | elapsed=16.1s legit report (2s cap) timeout=2s | CANCELED | elapsed=2.4s -> the 2s cap kills the report at ~2s with NO result; the work must be retried.
=== EDGE 2: statement_timeout does NOT cap a transaction (the timer resets per statement) === Six separate pg_sleep(1) inside ONE transaction, all under statement_timeout=2s:
txn of 6x pg_sleep(1) statement_timeout=2s | cancels=0 | elapsed=8.0s -> ZERO cancels, the transaction ran ~6s > the 2s 'cap'. Each statement reset the timer.
Contrast: ONE pg_sleep(6) under the SAME 2s cap:
single 6s statement (2s cap) timeout=2s | CANCELED | elapsed=2.4s
(Per-statement cap, not a transaction cap. To bound total transaction time use transaction_timeout, introduced in PostgreSQL 17.)
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
statement_timeout sets the maximum wall-clock duration any single statement is allowed to run before the server cancels it. When a command runs longer than the limit, the backend raises a NON-FATAL error — ERROR: canceling statement due to statement timeout — aborts that statement (and the surrounding transaction enters the aborted state), but the SESSION stays connected and can keep working after a ROLLBACK. The default is 0, which means the guard is OFF and a statement may run forever. Setting it to a positive number of milliseconds bounds how long a runaway query, an accidental cross join, a missing-index sequential scan, or a stuck report can tie up a backend, CPU, and the locks/snapshot it holds. It is a per-STATEMENT cap measured from when the command starts executing, and the timer RESETS on each new statement — so it caps individual statements, not the total length of a transaction (that is transaction_timeout, new in PG 17), and it does not touch a session that is merely idle (that is idle_session_timeout / idle_in_transaction_session_timeout). The value can be set per session, per role, per database, or cluster-wide, and takes effect immediately because context=user.
- name
statement_timeout
- setting default
0 (disabled)
- 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 statement.
- ground truth note
Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-20. setting=0, boot_val=0, reset_val=0 on EVERY version (default_changed=false) — the guard ships DISABLED. 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 statement.' Identical across 14, 15, 16, 17, 18. The related transaction_timeout (whole-transaction cap) is NEW in PG 17.
Rock-stable across versions: default=0 (disabled), min=0, max=2147483647, context=user, vartype=integer, unit=ms on PG 14, 15, 16, 17 and 18 — no version cliff for THIS knob (default_changed=false). The FAMILY changed: transaction_timeout (a cap on the whole transaction's duration, which statement_timeout cannot provide) was ADDED in PG 17, so on 17/18 a stronger total-duration guard is available alongside it. idle_session_timeout and idle_in_transaction_session_timeout are present and unchanged across 14-18. Per-version detail in _guc_matrix_machine.
context=user: set it per session with SET statement_timeout = '2s'; (effective immediately for the current session), per role/database with ALTER ROLE/DATABASE . SET (the usual way to apply it broadly without a restart — e.g. ALTER DATABASE app SET statement_timeout='30s'), 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 statement_timeout = -1; -> ERROR: -1 ms is outside the valid range for parameter "statement_timeout" (0 ms .. 2147483647 ms). The valid range is 0 . 2147483647 ms; 0 DISABLES the cap (the default). The value is in milliseconds but accepts unit suffixes like '2s', '500ms', '5min'.
Managed providers (Azure Database for PostgreSQL Flexible Server, Amazon RDS / Aurora, Cloud SQL, Crunchy, Supabase, Neon, Heroku) all expose statement_timeout as a server parameter, default 0, and because context=user you can also set it per-session/role/database without provider involvement (ALTER ROLE/DATABASE . SET, or SET LOCAL in a job). Some platforms and frameworks set a non-zero default at the connection or pooler layer (e.g. a web framework setting a few-second statement_timeout per request), so a query may be canceled by a layer you did not configure in postgresql.conf — check the role/database defaults and the pooler. Recommended posture: a small cap on application/web roles, a generous cap or none on reporting/ETL/admin roles, SET LOCAL for individual long jobs, and on PG 17+ add transaction_timeout where you need a total-transaction bound. Do not rely on a single global value.
Common Pitfalls
- Setting one aggressive cluster-wide value — it cancels legitimate long reports/migrations/index builds (EDGE 1). Prefer per-role/per-database values and SET LOCAL for individual jobs.
- Believing it caps a TRANSACTION — the timer resets per statement, so a transaction of many short statements runs unbounded (EDGE 2). Use transaction_timeout (PG 17+) for a total cap.
- Leaving the default 0 on an OLTP/web pool — one bad query (cross join, missing index) can tie up a backend, CPU, and locks indefinitely.
- Confusing it with lock_timeout — lock_timeout bounds only WAITING for a lock; statement_timeout bounds total execution time including the work after the lock is acquired.
- Confusing it with the idle timeouts — statement_timeout only fires while a statement is RUNNING; idle time inside or outside a transaction is governed by idle_in_transaction_session_timeout / idle_session_timeout.
- Sizing it below the slowest legitimate statement for a role — produces intermittent 'canceling statement due to statement timeout' errors on jobs that previously succeeded, usually right after someone tightened a default.
Evidence and References
- PostgreSQL docs: Runtime Config — Client Connection Defaults / Statement Behavior (statement_timeout — abort any statement that runs longer than the limit; 0 disables; the timer applies per statement; related lock_timeout, transaction_timeout, idle_in_transaction_session_timeout, idle_session_timeout).
- PostgreSQL 17 release notes: addition of transaction_timeout (a cap on total transaction duration, the guarantee statement_timeout does not provide because its timer resets per statement).
- PostgreSQL docs: lock_timeout (bounds time spent waiting for a lock — a subset of total statement time, distinct from statement_timeout).
- PostgreSQL docs: Error message 'canceling statement due to statement timeout' (the non-fatal cancellation raised when a statement exceeds the cap; the session survives after rollback).
- Lab capture 2026-06-20: — pg_sleep(10) under a 2s cap canceled at ~2.7s and a 20M-row md5 aggregate (17.3s) under a 1s cap canceled at ~1.5s, session survived ; 60s cap completed the 16s report but a 2s cap canceled it (EDGE 1), and 6x pg_sleep(1) under a 2s cap had 0 cancels while 1x pg_sleep(6) under the same cap was canceled (EDGE 2) ; boundary min=0 disables, range error on -1, context=user, identical /; pg_settings ground truth PG 14-18 (-18).
Technical Reference
- Category
- Client Connection Defaults / Statement Behavior
- Type
- integer (ms (milliseconds; 0 disables))
- Blast radius
- query
- Severity
- Moderate
- Context
- user
- Versions
- 14, 15, 16, 17, 18
- Reviewed
- 2026-06-20
Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.