Skip to main content
RunBook Academy

PostgreSQLII · Installation, Packaging and Service ManagementInstallation

Service management, and stopping PostgreSQL safely

Intermediate⏱ ~25 minpsqlpg_ctl

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

Not yet marked complete on this device.

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

ModeSignalWaits forNext start
SmartSIGTERMEvery client to disconnect voluntarilyClean
FastSIGINTIn-flight transactions to roll backClean
ImmediateSIGQUITNothingCrash 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.

Read-only / Safethe cluster state recorded in pg_control
$ pg_controldata $PGDATA | grep -i 'cluster state'
Database cluster state:               in production

The states worth recognising:

StateMeans
in productionRunning — or stopped without a clean shutdown
shut downCleanly shut down; next start will not recover
shut down in recoveryA standby, cleanly stopped
in archive recoveryReplaying WAL, as a standby or during PITR
starting upMid-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.

Read-only / Safethe log of an actual crash recovery
$ docker logs rbpg-crash
LOG:  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 connections

Read 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

  1. Use fast mode. Smart waits indefinitely for clients that may never leave; immediate defers the work to the next startup where it costs more.
  2. 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.
  3. Compare TimeoutStopSec against a timed shutdown of your largest cluster. A timeout shorter than the shutdown checkpoint converts every planned restart into an unintended crash recovery.
  4. Read pg_controldata before starting a cluster you found stopped. in production means recovery is coming and the startup will take longer.
  5. 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, TimeoutStopSec and the escalation to SIGKILL that 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

  1. Q1. A fast shutdown has been running for two minutes during a change window. What is the correct next action?

  2. Q2. pg_controldata reports 'Database cluster state: in production' for a cluster you know is stopped. What does this tell you?

  3. Q3. Which statements about PostgreSQL shutdown are correct? Select all that apply.

  4. Q4. If a change only alters sighup-context parameters, it can be applied with no client disruption at all.

  5. Q5. Explain why committed transactions survive an immediate shutdown even though no checkpoint is written.

  6. 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.