Nobody finds ignore_invalid_pages by reading the documentation front to back. You find it by pasting PANIC: WAL contains references to invalid pages into a search engine, generally at an hour when you would prefer to be asleep. It is a developer option, absent from postgresql.conf.sample, present since PostgreSQL 13. The default is off and the context is postmaster, which for once costs nothing: the server you would need to restart is already down.
To understand what the parameter does, you need to know what the PANIC means. During WAL replay, a record can reference a page that isn’t usable: either the block is past the end of the relation’s file (“page 1234 of relation base/16384/16385 does not exist”), or the page is within the file but still all zeros where the record expected real content (“…is uninitialized”). Replay is deliberately forgiving about the surrounding structure; it will create a missing relation file on the spot, on the theory that the relation is probably dropped later in the WAL stream and writing the data is safer than guessing. What it will not invent is page contents.
An invalid reference before recovery reaches consistency is not even an error. It’s expected. WAL is written before data files are changed, so replay routinely encounters writes to pages whose relation a later record removes; the on-disk files already reflect that later state. PostgreSQL stashes each reference in an in-memory hash table (the “XLOG invalid-page table” in xlogutils.c), skips applying that record to that block, and keeps going. When a subsequent record drops the relation, truncates away the block, or drops the whole database, the matching entries are deleted. The reference has been explained.
At the consistency point, the table must be empty. Anything left is a write to a page that never existed and was never legitimately removed, which means the data files and the WAL disagree about history, which means something has been lost. PostgreSQL emits a WARNING for each surviving entry and then the PANIC. On a standby that is already consistent and streaming, a new invalid reference PANICs immediately instead of waiting.
Notice what has to be true for either PANIC to fire: PostgreSQL must have a definition of “consistent” to enforce, and it only has one during archive recovery and standby operation, when minRecoveryPoint is set. A primary doing plain crash recovery never declares consistency mid-replay and never runs the final check; the table quietly evaporates when recovery ends. In practice this PANIC belongs to standbys, restored base backups, and point-in-time recovery. Which produces the characteristic presentation: a standby PANICs, the supervisor restarts it, replay reaches the same record, and it PANICs again, forever. The mailing list case that produced the parameter was exactly this: a VACUUM truncation whose WAL record was flushed, after which the vacuum was canceled before the primary actually shortened the file. The standby obeyed the WAL and truncated; the primary kept writing into blocks the standby no longer had. The standby was punished for following instructions. Fujii Masao added the escape hatch in response.
Here is the entire implementation of ignore_invalid_pages: those two PANICs become WARNINGs. Two error levels in xlogutils.c; nothing else in the server consults it. Nothing is repaired. The skipped writes stay skipped, because there is nothing to apply them to, and the database that opens is missing an unknown amount of data in unknown places: heap tuples without index entries, index entries without heap tuples, visibility information that lies.
So treat it as a crowbar. On a standby, don’t reach for it at all; a standby is a disposable copy, so re-clone it with pg_basebackup and investigate how the divergence happened. Never leave it on to “stop the crashing”; that converts a loud failure into silent, permanent divergence from the primary. When a restored backup or a PITR run hits the PANIC, the correct answer is a better backup or an earlier target. ignore_invalid_pages is for the case where there is no better copy: work on a scratch copy of the cluster, set it on, start, and take a pg_dump of what’s readable while trusting nothing. The WARNINGs name each victim by file path; once the server is up, pg_filenode_relation(0, 16385) (run in the right database) turns that into a table name, and pg_amcheck gives you a damage survey. Then the salvaged cluster goes in the bin, the dump goes into a fresh one, and the documentation’s warning that this setting can “cause crashes, data loss, propagate or hide corruption” goes back to being someone else’s problem.
Like ignore_checksum_failure, and like zero_damaged_pages (the same species of tool, for damaged pages on a server that’s actually running), this parameter exists to be set once, under protest, on a machine you’ve already decided to discard.