Home / Alt manpages / pg_ctl(1)

  • pg_ctl(1)
  • User command
  • linux

Operate a PostgreSQL 16 Cluster Safely with pg_ctl

You will use pg_ctl to initialise a PostgreSQL cluster, start it with a log file, verify its state, reload configuration, restart when necessary, and stop it without choosing a more disruptive shutdown than the situation requires. The examples match the PostgreSQL 16.15 installation on this machine. Allow 15 to 30 minutes for a first run, longer if you are working around an existing service or an unfamiliar data directory.

This guide assumes a Linux shell, the PostgreSQL server binaries, and a data directory represented below by /srv/postgres/example. Replace that path with the cluster you actually own. Most commands should run as the operating-system account that owns the cluster. Use sudo only to inspect or repair permissions when your normal account genuinely lacks access; do not run a database server as root.

1. Confirm the binary and the data directory

Start by finding the exact executable and version. On this installation the binary is in PostgreSQL's versioned bin directory rather than on the ordinary interactive PATH.

$ PGCTL=/usr/lib/postgresql/16/bin/pg_ctl
$ "$PGCTL" --version
pg_ctl (PostgreSQL) 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
$ DATA=/srv/postgres/example
$ test -d "$DATA" && echo "data directory exists"
data directory exists

pg_ctl needs a data directory for most modes. You can provide it with -D, or set PGDATA. An environment variable is convenient, but it can also send a command at the wrong cluster if you switch between projects, so print it before doing anything that changes state.

$ export PGDATA=/srv/postgres/example
$ printf 'PGDATA=%s\n' "$PGDATA"
PGDATA=/srv/postgres/example

Checkpoint: you have confirmed the PostgreSQL major version, the executable path, and the exact cluster directory. If the directory is not yours, stop here.

2. Initialise a new cluster only when the directory is disposable

Use initdb mode only for a new cluster. It creates the database files and configuration in the target directory. This is a destructive boundary: never point it at an existing cluster that contains data you need. Check the directory first, and prefer a new, explicitly named path.

$ NEW_DATA=/srv/postgres/example-new
$ test ! -e "$NEW_DATA" && echo "safe to initialise: path does not exist"
safe to initialise: path does not exist
$ "$PGCTL" init -D "$NEW_DATA"
initdb: warning: enabling "trust" authentication for local connections
Success. You can now start the database server using:

    pg_ctl -D /srv/postgres/example-new -l logfile start

The exact output depends on locale and the installed initdb defaults. The useful result is an exit status of zero and a newly created cluster. The example may enable local trust authentication, which is not a suitable default for every deployment. Review pg_hba.conf before exposing the server or placing sensitive data in it.

To undo a test cluster, stop it first if it is running, then remove only the explicit test directory after checking the path twice. Removing a cluster permanently deletes its databases. Keep a backup or use a disposable filesystem when testing.

3. Start the server and capture its log

Start the cluster in the background and append server output to a file. The manual recommends -l or an equivalent redirection because otherwise background output can remain attached to the terminal.

$ "$PGCTL" start -D "$DATA" -l "$DATA/server.log"
waiting for server to start.... done
server started

PostgreSQL 16 waits by default for startup to complete. A successful command means the server reached the ready state detected through its PID file, but it does not replace an application-level connection test. The default wait limit is 60 seconds unless PGCTLTIMEOUT is set.

$ "$PGCTL" status -D "$DATA"
pg_ctl: server is running (PID: 12345)
/usr/lib/postgresql/16/bin/postgres "-D" "/srv/postgres/example"
$ tail -n 20 "$DATA/server.log"

The PID and command line vary. If startup fails, read the log before retrying. Typical causes include a port already in use, an invalid configuration value, an inaccessible directory, or a stale postmaster.pid. Do not delete that PID file merely to make pg_ctl stop complaining: first establish whether the recorded process is alive and belongs to this cluster.

To test the database itself, use an installed client and the connection settings intended for your deployment:

$ psql -d postgres -c 'select version();'
                                                           version
-----------------------------------------------------------------------------------------------------------------
 PostgreSQL 16.15 ...
(1 row)

4. Reload configuration without a restart

Some changes in postgresql.conf and pg_hba.conf can be applied with a reload. Edit the configuration using your normal review and backup process, then send the server a SIGHUP through pg_ctl reload.

$ "$PGCTL" reload -D "$DATA"
server signaled
$ "$PGCTL" status -D "$DATA"
pg_ctl: server is running (PID: 12345)

A reload does not make restart-only settings active, and it does not prove that every edited line was accepted. Check the server log for configuration errors, then test the intended connection or setting. If the edit blocks clients, restore the previous file and reload again. Keep a copy of the last known-good configuration so recovery is a deliberate change rather than a hurried reconstruction.

5. Restart when a full stop and start is required

Use restart for settings that need a new server process or when the service has a planned maintenance window. It is effectively a stop followed by a start. By default, PostgreSQL reuses the prior server options recorded in postmaster.opts. Supplying -o replaces those options, so review the complete resulting command before changing ports, durability settings, or memory-related options.

$ "$PGCTL" restart -D "$DATA" -l "$DATA/server.log"
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
$ "$PGCTL" status -D "$DATA"
pg_ctl: server is running (PID: 23456)
/usr/lib/postgresql/16/bin/postgres "-D" "/srv/postgres/example" ...

Restart interrupts connections. Warn users and stop writes through the application first if the database carries production traffic. If the command times out, the operation may still finish in the background, so check status and the log before issuing another restart.

6. Stop the cluster with the least disruptive mode

Stopping a server is service-disrupting. The default fast mode rolls back active transactions, disconnects clients, and performs a proper shutdown. It is usually the practical maintenance choice.

$ "$PGCTL" stop -D "$DATA" -m fast
waiting for server to shut down.... done
server stopped
$ "$PGCTL" status -D "$DATA"; printf 'status exit: %s\n' "$?"
pg_ctl: no server running
status exit: 3

Choose smart when you can wait for existing clients to disconnect and want to refuse new connections while doing so. It can wait indefinitely in a busy system, so set an appropriate timeout and monitor the process. Choose immediate only for an emergency when a clean shutdown cannot complete. It aborts server processes and causes crash recovery on the next start; it is not a faster routine stop.

$ "$PGCTL" stop -D "$DATA" -m smart -t 120

If a smart stop does not complete, investigate active clients and decide whether a planned fast stop is acceptable. Do not jump straight to immediate mode because a command exceeded its wait. After any immediate shutdown, start the cluster and watch the log for recovery completion before allowing applications to reconnect.

7. Use no-wait only when another check owns the result

-W starts the requested action without waiting and returns no confirmation that it succeeded. This can be useful for a supervisor that checks health separately, but it is a common source of false success in scripts. Prefer the default wait behaviour for interactive maintenance, or follow a no-wait command with an explicit status and log check.

$ "$PGCTL" start -D "$DATA" -l "$DATA/server.log" -W
$ sleep 1
$ "$PGCTL" status -D "$DATA"

Use -t to change the maximum wait, or set PGCTLTIMEOUT for the default. Remember that a timeout is not proof that the server stopped or failed: the operation can continue in the background.

Done means

  • You confirmed the PostgreSQL 16.15 pg_ctl binary and the intended data directory.
  • You initialised only a new or disposable directory, and reviewed local authentication before use.
  • You start with a log file and verify both status and a real client connection.
  • You use reload for reloadable configuration and restart only when the server must be replaced.
  • You understand that fast is the normal stop, smart can wait, and immediate triggers recovery.
  • You check status and logs after timeouts instead of assuming that the requested action failed.