Cookbook recipe

Data File Corruption

Applies to PostgreSQL 13–17 Last reviewed May 2026 Grounded in source
Estimated investigation4 min

Scenario

Scenario After an unexpected server shutdown during heavy writes, queries against the orders table return ERROR: invalid page header in block 4523 of relation base/12345/67890. Other tables work fine. pg_dump fails when it reaches this table.…

Investigation Path

Scenario

After an unexpected server shutdown during heavy writes, queries against the orders table return ERROR: invalid page header in block 4523 of relation base/12345/67890. Other tables work fine. pg_dump fails when it reaches this table. The server had fsync = on (correct) and data checksums enabled, which is how the corruption was detected. The corruption appears to be in a few pages of one heap file.

How to Identify

Conditions:

  • ERROR: invalid page header in block N when querying a specific table
  • ERROR: wrong page type or ERROR: checksum mismatch (if checksums enabled)
  • pg_dump fails at the corrupted table
  • pg_filedump shows bad page headers for specific blocks
  • Only specific blocks affected (not entire table)

Analysis Steps

-- Find which table is corrupted (if you only have the error's block/relation info)
SELECT relname, relfilenode
FROM pg_class
WHERE relfilenode = 67890;   -- from the error message

-- Check if data checksums are enabled
SELECT current_setting('data_checksums');
-- 'on' = checksums enabled; corruption detected immediately on read
-- 'off' = corruption may go undetected for a long time (silent corruption)

-- Try to read pages from the table to find the extent of corruption:
SET zero_damaged_pages = off;  -- default: raises ERROR on bad pages
SELECT count(*) FROM orders;   -- will error at the first bad page

-- Try with zero_damaged_pages=on (zero out bad pages, continue reading):
SET zero_damaged_pages = on;
SELECT count(*) FROM orders;   -- returns count, bad pages silently zeroed
RESET zero_damaged_pages;

-- Check pg_catalog.pg_relation_filepath to find the file on disk:
SELECT pg_relation_filepath('orders');
-- Returns: 'base/12345/67890' (relative to $PGDATA)

Pitfalls

  • zero_damaged_pages = on allows PostgreSQL to continue past corrupted pages but silently discards all data in those pages. Use only to recover what’s salvageable — do not leave this enabled.
  • Data checksums catch corruption at read time. Without checksums, corrupted pages are silently used until the data is actually wrong.
  • Trying to VACUUM or REINDEX a table with corruption will fail or produce a broken index. Fix the table first.
  • pg_filedump (external tool) can inspect raw page contents without going through PostgreSQL — useful for forensics.
  • After recovering from corruption, verify the recovered data against a backup or secondary — rows from corrupted pages are lost even with zero_damaged_pages = on.

Resolution Approach

Restore the corrupted table from the most recent backup using PITR or pg_dump restore. If a backup isn’t available: use zero_damaged_pages = on to extract surviving rows into a new table, then rebuild the original. Always enable data checksums (initdb --data-checksums) to detect future corruption early.

This is a Pro lesson

Get every Learning Pathway and cookbook recipe — grounded in PostgreSQL source code, with diagnostics, fixes, and prevention for each topic.

Continue this lesson to learn:

  • Mitigation Actions
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

Career Impact

This scenario builds production judgment and operational confidence under pressure.

Open Career Dashboard →

Keep going

Related & next steps

Was this helpful?

← All cookbook recipes