Reported symptoms
At 02:14 the cluster stops accepting connections entirely. The application returns connection refused.
The last line in the server log is a PANIC about no space left on
device. df reports the data volume at 100 percent, zero bytes
available.
Starting the cluster produces the same PANIC within a second and the
postmaster exits again.
pg_wal holds 1.4 GB of segments against a max_wal_size of 1 GB, and
88 files carry a .ready marker.
The first suggestion in the incident channel is to delete the oldest
files in pg_wal. The second is to set fsync = off so the cluster can
start.
Evidence provided
$ tail -4 /var/lib/postgresql/18/main/log/postgresql.log2026-08-28 02:14:07.882 UTC [61] PANIC: could not write to file "pg_wal/xlogtemp.61": No space left on device
2026-08-28 02:14:07.909 UTC [59] LOG: checkpointer process (PID 61) was terminated by signal 6: Aborted
2026-08-28 02:14:07.909 UTC [59] LOG: terminating any other active server processes
2026-08-28 02:14:07.933 UTC [59] LOG: database system is shut down$ df -h /var/lib/postgresqlFilesystem Size Used Avail Use% Mounted on
/dev/vdb 20G 20G 0 100% /var/lib/postgresqlIllustrative output
$ du -sh pg_wal && ls pg_wal/archive_status | grep -c ready1.4G pg_wal
88The archive_command writes to an NFS mount that has been unavailable
since 22:40 the previous evening. The log has carried archive command failed with exit code 1 roughly every fifteen seconds since
22:41.
No alert was raised for the archive failures. The disk-space alert fired at 95 percent at 01:58 — sixteen minutes before the PANIC.
pg_replication_slots is empty, so this is not an abandoned slot.
Work the evidence before reading on
max_wal_sizeis 1 GB andpg_walholds 1.4 GB. Is that a bug?- Three and a half hours passed between the first archive failure and the outage. What consumed that warning?
- The disk alert fired at 95 percent. Was that alert useful?
- What happens if somebody deletes the oldest 20 files in
pg_walright now?
Root cause
PostgreSQL did the right thing at every step
The archive destination went away at 22:40. PostgreSQL refused to recycle any segment that had not been successfully archived, retried every fifteen seconds, and logged every failure. When the volume filled and it met a write it could not complete durably, it PANICked and stopped.
The failure chain is mostly monitoring
| Time | Event | Was it alerted? |
|---|---|---|
| 22:40 | Archive destination unreachable | No |
| 22:41 → | archive command failed every 15s | No |
| 22:41 → 02:14 | WAL accumulating, 3h33m | No |
| 01:58 | Disk at 95 percent | Yes — 16 minutes of headroom |
| 02:14 | PANIC, cluster down | Yes |
Three and a half hours of warning existed and was written to the log the entire time. The one alert that did fire arrived too late to act on, because 95 percent of a volume filling at this rate is sixteen minutes, not a buffer.
The dangerous part of this incident is the proposed fix
Resolution
Free space from somewhere that is not pg_wal:
- Grow the volume. On cloud or LVM storage this is minutes, and it is always the correct first answer.
- Remove non-PostgreSQL files from the same volume — old logs, a
forgotten dump, a core file.
du -sh /var/lib/postgresql/*finds them in seconds. - Delete the ballast file, if one exists. That is exactly what it is for.
Then fix the archive, because until archive_command succeeds the volume
fills again:
sudo -u postgres test -w /mnt/wal-archive && echo writable
archive_command is reloadable:
ALTER SYSTEM SET archive_command = '/usr/local/bin/archive-wal.sh %p %f';
SELECT pg_reload_conf();
Start the cluster. It replays from the last checkpoint, then works through the 88 pending segments. Watch the backlog drain:
watch -n 5 'ls /var/lib/postgresql/18/main/pg_wal/archive_status | grep -c ready'
The incident is over when that count reaches zero and pg_wal has
fallen back toward max_wal_size — not when the cluster starts.
Verification
The cluster accepts connections and pg_is_in_recovery() returns false.
The archive backlog is zero and the archiver is succeeding:
SELECT archived_count, last_archived_wal, last_archived_time,
failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;
du -sh pg_wal has returned to roughly max_wal_size plus normal churn.
df -h shows hours of headroom at the observed WAL rate, not a
percentage.
Force a round trip and confirm the segment lands at the destination:
SELECT pg_switch_wal();
A quiet pg_stat_archiver looks the same whether the archive is healthy
or nothing is being archived at all. Test it.
Prevention
Alert on pg_stat_archiver.failed_count increasing. This is the
missing alert that cost three and a half hours of warning.
Alert on the archive_status ready count. It measures the backlog
rather than individual failures, so it catches an archive that succeeds
intermittently but cannot keep up.
Express the disk alert in time, not percent. Ninety-five percent of 20 GB was sixteen minutes. Alert below several hours of measured WAL generation.
Give pg_wal its own filesystem. A full pg_wal still stops the
cluster, but it stops only the cluster, and the growth becomes its own
visible metric.
Keep a ballast file — a few pre-allocated gigabytes, deletable in one command. Document where it is and who may delete it.
Write down that WAL is never deleted by hand, in the runbook, where the person at 02:14 will read it. That suggestion arrived within minutes here because it is the obvious wrong answer, and obvious wrong answers must be pre-empted in writing rather than argued about mid-outage.