Reported symptoms
A nightly reconciliation query began failing with ERROR: invalid page in block 5 of relation "base/5/16384".
Most queries against the same table work normally, including primary-key lookups. The application is otherwise unaffected and no other table produces errors.
An engineer found ignore_checksum_failure, set it, and the
reconciliation query now completes. Its totals have been used for two
nights.
The storage team reports a firmware update on the array eleven days ago and no reported errors since.
A second cluster in the same estate, built with checksums disabled, shows no errors at all.
Evidence provided
$ grep -E 'page verification|invalid page' /var/log/postgresql/postgresql-18-main.log2026-08-27 23:47:33.213 UTC [78] LOG: page verification failed, calculated checksum 23084 but expected 18652
2026-08-27 23:47:33.213 UTC [89] ERROR: invalid page in block 5 of relation "base/5/16384"$ pg_checksums --check -D /var/lib/postgresql/corpg_checksums: error: checksum verification failed in file
"/var/lib/postgresql/cor/base/5/16384", block 5:
calculated checksum 5A2C but block contains 48DC
Checksum operation completed
Files scanned: 955
Blocks scanned: 3212
Bad checksums: 1
Data checksum version: 1The damage is confined: SELECT count(*) FROM victim fails, and
SELECT count(*) FROM victim WHERE id = 1 succeeds.
$ psql -c "SET ignore_checksum_failure = on; SELECT count(*) FROM victim;"WARNING: ignoring checksum failure in block 5 of relation "base/5/16384"
count
-------
20000And on the cluster built without checksums, with the identical damage:
$ psql -c "SELECT count(*) FROM victim;" -c "SELECT id, payload FROM victim WHERE payload !~ '^row-[0-9]+-z+$';" count
-------
20000
id | payload | len
-----+----------------------------------------------------------------------+-----
423 | row-423-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzCORRUPTXzzzzzzzzzzzzzzzzz | 68Work the evidence before reading on
- Most queries succeed. Is the damage smaller than it looks?
ignore_checksum_failuremade the query work. What did it repair?- The second cluster reports nothing. Is it healthier?
- Two nights of reconciliation totals came from block 5. What do you do about that first?
Root cause
A checksum caught a damaged page and PostgreSQL refused to serve it
That is the entire purpose of the feature, working. The block was corrupted below the database — the firmware update eleven days ago is the obvious candidate, though establishing that is the storage team’s work. The page read back differs from the page written.
pg_checksums found one bad block out of 3,212. Queries that do not read
block 5 succeed, which is why most of the application never noticed.
The setting repaired nothing
The cluster without checksums is the important evidence
Why the error came and went
Resolution
Turn the setting off, and treat everything produced while it was on as unreliable:
ALTER SYSTEM SET ignore_checksum_failure = off;
SELECT pg_reload_conf();
Withdraw the two nights of totals. Communication task, and the most urgent thing in this incident.
Establish the extent of the damage against the files:
pg_ctl -D /var/lib/postgresql/18/main -m fast stop
pg_checksums --check -D /var/lib/postgresql/18/main
One bad block is a different incident from four hundred, and the errors alone cannot tell you which you have.
Identify what the block holds, then check the structures checksums do not cover:
SELECT relname, relkind FROM pg_class WHERE relfilenode = 16384;
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check(index => c.oid, heapallindexed => true)
FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
WHERE c.relkind = 'i' AND i.indisvalid;
bt_index_check returns void on success and raises an error describing
the problem otherwise.
Then choose the repair, in this order:
- Restore from backup and replay. The only option producing a cluster you can trust completely. If the damage predates your oldest backup, a point-in-time recovery to before the corruption is the same answer.
- Rebuild the object, if it is an index. An index is derived data;
REINDEXregenerates it from the heap and is correct by construction. - Salvage the readable rows and rebuild the table, if the damage is
in a heap block and no backup helps. This is where
ignore_checksum_failurelegitimately appears — as part of a salvage, with the extracted data reconciled against another source.
Verification
pg_checksums --check reports zero bad checksums across the whole
cluster, run against a stopped cluster or a restored copy.
amcheck passes on every index of the affected table, with
heapallindexed => true so the heap is compared against the index rather
than only the index’s own structure.
The failing query completes and returns a value that reconciles against an independent source. A query that stops erroring is not the same as a query that is correct.
ignore_checksum_failure is off everywhere, verified rather than
assumed:
SELECT name, setting, source FROM pg_settings WHERE name = 'ignore_checksum_failure';
checksum_failures is zero and stays zero — remembering that this
counter means nothing on a cluster without checksums:
SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database;
The storage team has identified a cause, and the withdrawn totals have been recomputed from a trusted copy with the difference known.
Prevention
Run with data checksums on. Enabled by default from PostgreSQL 18
onward; pg_checksums --enable turns them on offline for an existing
cluster. The cost is a few percent of CPU. The benefit is the difference
between these two outcomes on identical damage:
with checksums: ERROR: invalid page in block 5 of relation "base/5/16384"
without checksums: 20000 <- no error, no warning, nothing
Never leave ignore_checksum_failure on.
Alert on checksum_failures and on page verification failed. Either
would have caught this on the first night.
Alert on invalid page in block — the error the application sees, and
it names the relation.
Run pg_checksums --check on a schedule against a restored backup,
which verifies the backup and the checksums in one pass and costs
production nothing.
Run amcheck periodically on important indexes. Checksums cover
pages; amcheck covers structure, and a logically corrupt index passes
every checksum.
Know that checksums are verified on read from disk, not from shared buffers.
Treat one bad block as a storage incident. The database’s job here is detection; the repair belongs upstream.
Test restores. Every remedy that produces a trustworthy cluster begins with a backup you can restore, and this is the incident in which you find out whether you have one.