PostgreSQLII · Installation, Packaging and Service ManagementInstallation
Service management, and stopping PostgreSQL safely
What you'll learn
- Distinguish the smart, fast and immediate shutdown modes and their consequences
- Map each shutdown mode to the signal and the tooling that requests it
- Explain why a shutdown timeout that expires is worse than a slow shutdown
- Verify that a cluster shut down cleanly before assuming it did
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
Stopping a database is not one operation. PostgreSQL offers three shutdown modes that differ in how much they are willing to wait for and what state they leave behind, and the difference between the gentlest and the harshest is the difference between a clean restart and a recovery.
Most operators never choose a mode. It is chosen for them by the systemd unit, by a container runtime’s stop signal, or by whoever decided a shutdown was taking too long.
The three modes
| Mode | Signal | Waits for | Next start |
|---|---|---|---|
| Smart | SIGTERM | Every client to disconnect voluntarily | Clean |
| Fast | SIGINT | In-flight transactions to roll back | Clean |
| Immediate | SIGQUIT | Nothing | Crash recovery |
Smart is the polite one. The server stops accepting new
connections and then waits — indefinitely by default — for existing
clients to disconnect on their own. An application holding an idle
connection pool will hold the shutdown open forever, which is why smart
mode is rarely what an operator wants and is no longer the default for
pg_ctl.
Fast is the operational default and the right one in nearly every case. It disconnects clients, rolls back their in-flight transactions, writes a shutdown checkpoint, and exits. Committed data is durable, uncommitted work is discarded, and the next start is clean.
Immediate does not roll anything back and does not checkpoint. It signals every process to exit at once. Committed transactions are still safe — that is what the write-ahead log guarantees — but the data files are left in an inconsistent state, so the next start performs crash recovery.
flowchart TD
S["Shutdown requested"] --> M{"Which mode?"}
M -- "smart / SIGTERM" --> W["Refuse new connections\nwait for clients to leave"]
M -- "fast / SIGINT" --> D["Disconnect clients\nroll back open transactions"]
M -- "immediate / SIGQUIT" --> K["Signal all processes\nto exit at once"]
W --> C["Shutdown checkpoint"]
D --> C
C --> CS["State: shut down\nnext start is clean"]
K --> IS["State: in production\nnext start replays WAL"]
Why immediate is not merely slower
The cost of an immediate shutdown is not paid at shutdown. It is paid at the next start, and it is paid in time that is difficult to predict.
Crash recovery replays the write-ahead log from the last checkpoint to
the end. How long that takes depends on how much WAL accumulated since
that checkpoint, which depends on the write rate and the checkpoint
configuration. On a busy cluster with max_wal_size set generously —
which is what Part XII will recommend for other good reasons — this can
be minutes.
Those minutes are downtime during which the server is not accepting connections, and it reports why:
FATAL: the database system is starting up
An operator who chose immediate mode to make the shutdown finish quickly has traded a bounded wait for an unbounded one, at the point where the service is already down.
What each tool actually sends
# pg_ctl: the mode is explicit. Fast is the default from PostgreSQL 9.5.
pg_ctl -D "$PGDATA" stop -m fast
pg_ctl -D "$PGDATA" stop -m smart
pg_ctl -D "$PGDATA" stop -m immediate
# Debian wrapper: same modes, addressed by version and cluster name
pg_ctlcluster 18 main stop --mode fast
# systemd: the unit decides, and the unit is where to look
systemctl stop postgresql@18-main # Debian
systemctl stop postgresql-18 # Red Hat
The systemd case is the one worth inspecting rather than assuming, because the unit encodes both the signal and a timeout:
systemctl cat postgresql@18-main 2>/dev/null | grep -iE 'KillSignal|TimeoutStopSec|ExecStop'
systemctl show postgresql-18 -p TimeoutStopUSec -p KillSignal 2>/dev/null
TimeoutStopSec is the parameter that matters most and is least often
examined. When it expires, systemd escalates to SIGKILL — which is
harsher than immediate mode, because it does not even let the postmaster
signal its children in an orderly way. The result is the same crash
recovery, arrived at without anyone choosing it.
Verifying that a shutdown was clean
Do not assume. The control file records the answer, and it can be read while the cluster is stopped.
$ pg_controldata $PGDATA | grep -i 'cluster state'Database cluster state: in productionThe states worth recognising:
| State | Means |
|---|---|
in production | Running — or stopped without a clean shutdown |
shut down | Cleanly shut down; next start will not recover |
shut down in recovery | A standby, cleanly stopped |
in archive recovery | Replaying WAL, as a standby or during PITR |
starting up | Mid-startup |
Reading in production on a cluster you believe is stopped is the
signal that the next start will perform recovery. That is worth knowing
before you start it, because it tells you the startup will take
longer than usual and that the delay is expected rather than a fault.
The server log states it plainly at the next start. This is a real
capture: a PostgreSQL 18.6 cluster holding 200,000 rows was sent
SIGQUIT and then restarted.
$ docker logs rbpg-crashLOG: database system was interrupted; last known up at 2026-08-27 17:31:08 UTC
LOG: database system was not properly shut down; automatic recovery in progress
LOG: redo starts at 0/1761990
LOG: invalid record length at 0/4A0E7D8: expected at least 24, got 0
LOG: redo done at 0/4A0E390 system usage: CPU: user: 0.04 s, system: 0.01 s, elapsed: 0.05 s
LOG: checkpoint starting: end-of-recovery immediate wait
LOG: checkpoint complete: wrote 5915 buffers (36.1%), ... distance=51891 kB
LOG: database system is ready to accept connectionsRead that sequence as the whole story of a recovery: it was
interrupted, it knew it, it replayed from 0/1761990 to 0/4A0E390,
it checkpointed, and it opened for business. All 200,000 rows were
present afterwards, because every one of them had been committed.
A restart procedure should capture these lines, because their absence is how you confirm a shutdown really was clean.
Starting, reloading and restarting
# Reload configuration without disconnecting anyone. Applies sighup parameters.
psql -U postgres -c 'SELECT pg_reload_conf()'
# or
pg_ctl -D "$PGDATA" reload
systemctl reload postgresql@18-main
# Restart, which is a stop and a start with all the above applying to the stop
pg_ctl -D "$PGDATA" restart -m fast
pg_reload_conf() is worth preferring over the packaging-specific
alternatives in runbooks, for the reason the previous lesson gave: it
works identically on every layout and does not depend on a unit name.
It sends the same SIGHUP the other forms do.
A reload never disconnects a client and never interrupts a query. If a
procedure’s only change is to sighup-context parameters, it needs no
outage at all — which is a conversation worth having with a change
board that treats every database change as a restart.
Production discipline
- Use fast mode. Smart waits indefinitely for clients that may never leave; immediate defers the work to the next startup where it costs more.
- Never escalate a slow shutdown to immediate without knowing what it is waiting for. The work does not disappear; it moves to recovery, where clients are locked out while it runs.
- Compare
TimeoutStopSecagainst a timed shutdown of your largest cluster. A timeout shorter than the shutdown checkpoint converts every planned restart into an unintended crash recovery. - Read
pg_controldatabefore starting a cluster you found stopped.in productionmeans recovery is coming and the startup will take longer. - Prefer
pg_reload_conf()in runbooks. It is packaging-independent and it makes clear that a reload is not a restart.
Cross-course references
- Linux for Production Sysadmins — Part VII (systemd) covers unit
files,
TimeoutStopSecand the escalation toSIGKILLthat this lesson warns about, and Part VI (Processes) covers the signals themselves. - Docker & Containers — Part VI (Container lifecycle) covers
docker stop, its default grace period and which signal it sends, which is the container form of exactly this question. - Kubernetes for Production Sysadmins — Part X (Termination) covers
terminationGracePeriodSeconds, which is the same timeout problem again with a different name.
Quiz
Knowledge check · 6 questions
Q1. A fast shutdown has been running for two minutes during a change window. What is the correct next action?
Q2. pg_controldata reports 'Database cluster state: in production' for a cluster you know is stopped. What does this tell you?
Q3. Which statements about PostgreSQL shutdown are correct? Select all that apply.
Q4. If a change only alters sighup-context parameters, it can be applied with no client disruption at all.
Q5. Explain why committed transactions survive an immediate shutdown even though no checkpoint is written.
Q6. Diagnose the pattern and state the fix.
A team reports that PostgreSQL restarts during monthly patching always take between four and nine minutes to become available, on a cluster where startup is otherwise instant. The server log at each start contains a line stating the database system was not properly shut down and that automatic recovery is in progress. The systemd unit has TimeoutStopSec set to 90 seconds. The cluster is 1.2 TB with max_wal_size raised to 16 GB to smooth checkpoint I/O during a bulk ingestion workload that runs continuously.
Passing score: 75%. Answers are checked in this browser.