This error occurs when PostgreSQL is in recovery mode (e.g., on a Hot Standby replica), which restricts write operations. The database must either complete recovery or be promoted to primary mode to execute write commands.
PostgreSQL enters recovery mode during several scenarios: after an unclean shutdown, when operating as a Hot Standby (read-only replica), or during automatic crash recovery. When in recovery mode, the database is read-only and only allows SELECT queries. Any attempt to execute Data Manipulation Language (DML) commands like INSERT, UPDATE, DELETE, TRUNCATE, or COPY FROM will fail with this error. The database uses WAL (Write-Ahead Log) files to replay transactions, and write operations cannot be executed until recovery is complete or the standby is promoted to primary.
Connect to the database and run:
SELECT pg_is_in_recovery();If this returns true, your database is in recovery/read-only mode. If it returns false, the error is likely due to default_transaction_read_only being enabled.
If you have a replication setup with a primary and standby, ensure you are connecting to the primary database for write operations:
psql -h primary-host -U username -d dbnameWrite operations must be executed on the primary database. The standby is strictly read-only and will always reject write operations.
If pg_is_in_recovery() returns false but you still get the error, check the database setting:
SELECT current_setting('default_transaction_read_only');If it shows on, disable it:
ALTER DATABASE your_database SET default_transaction_read_only = off;Then reconnect to apply the change.
If the database is still recovering after a crash, monitor the recovery progress by checking:
SELECT pg_is_wal_replay_paused();
SELECT pg_last_wal_receive_lsn();If recovery is paused, resume it:
SELECT pg_wal_replay_resume();Recovery can take significant time depending on WAL file size. Allow it to complete before attempting write operations.
If your standby should become the new primary (e.g., after failover), promote it using the pg_ctl utility:
On the standby server:
sudo -u postgres pg_ctl promote -D /var/lib/postgresql/dataOr use SQL on the standby:
SELECT pg_promote();This will exit recovery mode and allow the database to accept write operations. Update your application connection strings to point to this server once promotion is complete.
If recovery gets stuck, check and modify the recovery action setting in postgresql.conf:
ALTER SYSTEM SET recovery_target_action = 'promote';
SELECT pg_reload_conf();This setting determines what happens when recovery is complete:
- promote: Exits recovery and allows write operations (for standby)
- pause: Pauses recovery, requiring manual SELECT pg_wal_replay_resume();
- shutdown: Shuts down after recovery
For standby servers, promote is typically the right choice.
Hot Standby vs. Recovery Mode: In a streaming replication setup, the standby is always technically "in recovery" (replaying WAL), but the error usually means the standby has not been promoted to primary. Hot Standby mode allows read queries but strictly forbids writes.
Temporary Tables and Locks: Even creating temporary tables or acquiring row locks (SELECT FOR UPDATE) will fail on a standby, as they require updating system catalog files.
Two-Phase Commits: PREPARE TRANSACTION, COMMIT PREPARED, and ROLLBACK PREPARED are not allowed during recovery, even though they might appear read-only.
dblink and External Operations: Some PL functions using dblink or external tools can still write outside the database, even in read-only mode, but are rarely used for this purpose.
Hardware Failures: If recovery is stuck indefinitely and refuses to complete, this may indicate hardware problems (RAM corruption, disk errors, or filesystem corruption). Consider checking system logs and running disk checks before trying repeated restarts.
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL
SSL error: certificate does not match host name
SSL error: certificate does not match host name in PostgreSQL
No SSL connection
No SSL connection to PostgreSQL