checkpoint_timeout
Max time between checkpoints. Too short: I/O overhead. Too long: crash recovery takes minutes. 5min default balances both.
Tier 1 — Lab-Proven — Measured behavior and safety boundaries are available below.
What We Observed
box
cl_ckpt_short/cl_ckpt_long (--memory=1g, PG 17.10), pgbench TPC-B scale 5, -c 4 -j 2 -T 30
short 30s
- tps
1263.8
- latency ms
3.16
- stddev ms
2.118
- note
min valid value=30s (5s rejected)
default 300s
- tps
1092.2
- latency ms
3.658
- stddev ms
8.435
redo distance
- 30s
53MB (0.42s crash recovery)
- 3600s
930MB (much longer crash recovery)
- source
formula curve
# FIXED WORK: -t 40000 -c 4 = 160000 txns per setting; max_wal_size=16GB (time-driven); scale=50; img=postgres:17
checkpoint_timeout tps num_timed log_timed buffers_written wal_records wal_fpi wal_MB 30s 1031.750729 5 5 19777 1047974 179217 1417.2 60s 873.660878 3 3 5696 1048766 147971 1187.7 120s 808.770060 1 1 947 1047752 127656 1036.0 300s 1596.810068 0 0 0 1049988 84034 710.6
crash evidence
================= checkpoint_timeout=30s (SHORT timeout -> small redo distance -> FAST recovery) ================= -- loading WAL for 240s with NO checkpoint allowed (timeout=30s, max_wal_size=16GB) -- redo distance since last checkpoint = 53.0 MB | timed checkpoints during run = 8 -- SIGKILL (crash) -- -- recovery log (literal) --
2026-06-19 15:30:46.656 UTC [55] LOG: database system is ready to accept connections 2026-06-19 15:30:46.971 UTC [1] LOG: database system is ready to accept connections 2026-06-19 15:34:59.429 UTC [29] LOG: database system was not properly shut down; automatic recovery in progress 2026-06-19 15:34:59.432 UTC [29] LOG: redo starts at 0/B7D98E58 2026-06-19 15:34:59.856 UTC [29] LOG: invalid record length at 0/C4D9A8D0: expected at least 24, got 0 2026-06-19 15:34:59.856 UTC [29] LOG: redo done at 0/C4D9A8A8 system usage: CPU: user: 0.26 s, system: 0.14 s, elapsed: 0.42 s 2026-06-19 15:35:00.232 UTC [1] LOG: database system is ready to accept connections
================= checkpoint_timeout=3600s (LONG timeout (1h) -> large redo distance -> SLOW recovery) ================= -- loading WAL for 240s with NO checkpoint allowed (timeout=3600s, max_wal_size=16GB) -- redo distance since last checkpoint = 930.6 MB | timed checkpoints during run = 0 -- SIGKILL (crash) -- -- recovery log (literal) --
2026-06-19 15:35:03.122 UTC [56] LOG: database system is ready to accept connections 2026-06-19 15:35:03.439 UTC [1] LOG: database system is ready to accept connections 2026-06-19 15:39:15.061 UTC [29] LOG: database system was not properly shut down; automatic recovery in progress 2026-06-19 15:39:15.065 UTC [29] LOG: redo starts at 0/27EEE130 2026-06-19 15:39:20.320 UTC [29] LOG: invalid record length at 0/62188D90: expected at least 24, got 0 2026-06-19 15:39:20.320 UTC [29] LOG: redo done at 0/62188D68 system usage: CPU: user: 2.79 s, system: 2.42 s, elapsed: 5.25 s 2026-06-19 15:39:21.058 UTC [1] LOG: database system is ready to accept connections
== teardown ==
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
checkpoint_timeout is the maximum wall-clock time PostgreSQL will let pass between automatic checkpoints. A checkpoint is the point at which all dirty shared buffers are flushed to the data files and a record is written to WAL marking 'everything before here is safely on disk' — so on crash recovery, replay only has to start from the last completed checkpoint's redo point, not the beginning of time. The checkpointer fires a checkpoint when EITHER checkpoint_timeout elapses OR max_wal_size of WAL has been written since the last one, whichever comes first. checkpoint_timeout is the TIME lever (max_wal_size is the VOLUME lever). It defaults to 300 s (5 minutes). The value sits on a genuine two-sided tradeoff: a SHORTER timeout means more frequent checkpoints, which keeps crash-recovery fast (less WAL to replay) but amplifies steady-state WAL writes — because full_page_writes re-arms at every checkpoint, so the first modification to each page after a checkpoint emits a full-page image (FPI) into WAL. A LONGER timeout means fewer checkpoints, less FPI write amplification, and lower steady-state I/O, but a larger redo distance and therefore a longer crash recovery (longer outage). This lab MEASURED both halves of that tradeoff.
- name
checkpoint_timeout
- setting default
300
- unit
s (300 s = 5 min default)
- vartype
integer
- category
Write-Ahead Log / Checkpoints
- context
sighup
- min val
30
- max val
86400
- boot val
300
- reset val
300
- pending restart
false
- short desc
Sets the maximum time between automatic WAL checkpoints.
- ground truth note
Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-19. setting=300, boot_val=300, reset_val=300 (= 5 minutes) on EVERY version — default_changed=false, boot_fallback_changed=false. context=sighup (reload, no restart), vartype=integer, unit=s, min_val=30 s, max_val=86400 s (24 h) identical on all five versions. The 5-minute default and every surrounding fact are unchanged 14→18.
checkpoint_timeout is one of the most STABLE parameters in PostgreSQL: default 300 s (5 min), context=sighup, vartype=integer, unit=s, min_val=30, max_val=86400 are IDENTICAL on PG 14, 15, 16, 17, 18 (boot_val==reset_val==300 every version; default_changed=false, boot_fallback_changed=false). Its companions in the same category are equally stable across 14→18: max_wal_size=1024 MB (the volume trigger), checkpoint_completion_target=0.9 (spreads the checkpoint write over 90% of the interval), and full_page_writes=on (the mechanism behind the FPI write amplification measured below). The thing that has shifted across versions is not this knob but the surrounding observability: log_checkpoints defaults to ON from PG 15+, and PG 17 renamed/repackaged the checkpoint counters into pg_stat_checkpointer (num_timed / num_requested) — which this lab used and cross-validated against the log. Per-version detail in _guc_matrix_machine.
context=sighup means a config reload, no restart: edit postgresql.conf (or ALTER SYSTEM SET checkpoint_timeout='15min';) then SELECT pg_reload_conf; and the checkpointer picks up the new interval on its next loop. No connection drop, no downtime. Accepted forms include '300', '300s', '5min', '15min', '1h' (the lab booted containers with -c checkpoint_timeout=30s/300s/3600s and confirmed the in-effect value with SHOW checkpoint_timeout). Because it is sighup (not user), a session cannot change it for itself — it is a cluster-wide setting. Practical note proven in the lab: raising checkpoint_timeout ONLY changes behavior if max_wal_size is large enough that the VOLUME trigger does not fire first; with a small max_wal_size the volume trigger pre-empts the timer and the timeout change does nothing (that is why every experiment here held max_wal_size=16GB). BOUNDARY PROOF (captured literal): an out-of-range / invalid value is rejected at set time -- ALTER SYSTEM SET checkpoint_timeout = 29; -> ERROR: 29 s is outside the valid range for parameter "checkpoint_timeout" (30 s .. 86400 s). It is context=sighup, so a session-level set is refused -- SET checkpoint_timeout = ... -> ERROR: parameter "checkpoint_timeout" cannot be changed now; change it in postgresql.conf / ALTER SYSTEM and reload (pg_reload_conf), no restart.
On managed PostgreSQL (Amazon RDS, Google Cloud SQL/AlloyDB, Azure Database for PostgreSQL Flexible Server, Neon) checkpoint_timeout is a normal parameter-group / server-parameter setting and is sighup, so changing it does not reboot the instance. Providers usually keep the 300 s default but tune max_wal_size by instance size — remember the two are coupled, so raising checkpoint_timeout without a large enough max_wal_size yields no benefit. The recovery-time HALF of the tradeoff differs on Amazon Aurora, whose log-structured distributed storage does not do traditional single-node WAL redo replay on restart — there the FPI write-amplification reasoning still applies but the 'long timeout = slow crash recovery' danger is largely absorbed by the storage layer. On standard managed Postgres (RDS non-Aurora, Cloud SQL, Azure Flexible Server, Neon) both halves apply as measured here.
Common Pitfalls
- Raising checkpoint_timeout without raising max_wal_size — the VOLUME trigger fires first and the timeout change does nothing (proven; the lab held max_wal_size=16GB to avoid exactly this).
- Setting it very high (e.g. 1h) to save WAL — redo distance and crash-recovery time balloon (measured: 930.6 MB redo → 5.25 s recovery vs 53.0 MB → 0.42 s), blowing your RTO; far worse on production-sized WAL and slow storage.
- Assuming shorter is always safer — a short timeout amplifies steady-state WAL (~2× the WAL for the same work at 30 s vs 300 s here) and increases checkpoint I/O.
- Confusing checkpoint_timeout (time trigger) with max_wal_size (volume trigger) — checkpoints fire on whichever comes first; you must reason about both.
- Forgetting it is sighup — ALTER SYSTEM SET + SELECT pg_reload_conf is enough; no restart needed (and a session cannot set it for itself).
- Disabling full_page_writes to cut the FPI cost — that risks torn-page corruption on crash; use wal_compression instead if you need smaller FPIs.
- Benchmarking the WAL effect with a fixed-TIME load (pgbench -T) — faster settings do more work and the FPI signal is swamped; you MUST use fixed WORK (pgbench -t). This lab's first run failed exactly this way.
Evidence and References
- PostgreSQL 18 docs — Write Ahead Log / Checkpoints: checkpoint_timeout, max_wal_size, checkpoint_completion_target (runtime-config-wal.html)
- PostgreSQL 18 docs — full_page_writes: full pages written to WAL on first modification after a checkpoint (runtime-config-wal.html)
- PostgreSQL docs — WAL Internals / Reliability: crash recovery replays WAL starting from the last checkpoint's redo point
- PostgreSQL docs — The Cumulative Statistics System: pg_stat_wal (wal_fpi, wal_bytes, wal_records), pg_stat_checkpointer (num_timed) — the counters this lab measured and cross-checked against log_checkpoints
- Captured live on PG 14-18 (-18) ground truth + postgres:17 effect/crash labs (cl_ckpt, cl_ckptcrash), 2026-06-19, sev1db-config-eval
Technical Reference
- Category
- Write-Ahead Log / Checkpoints
- Type
- integer (s (300 s = 5 min default))
- Blast radius
- cluster
- Severity
- Moderate
- Context
- sighup
- Versions
- 14, 15, 16, 17, 18
- Reviewed
- 2026-06-19
Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.