statement_timeout

Cancels any statement exceeding the time limit (ms). Default 0 = no limit. Too aggressive kills legitimate analytics and VACUUM.

MEDIUM RISK
Safe first move

0 (default). Set 30s-300s for OLTP. Use per-role for analytics: ALTER ROLE analytics SET statement_timeout=0.

Main risk

Too low (2s): legitimate 16s report canceled at 2.4s (lab-captured). Too high (0/unlimited): runaway queries block table locks and consume resources indefinitely.

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
Unlock Incident Pro

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.

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.

Keep going

Related & next steps