PostgreSQL already writes a PID file. Every time the postmaster starts, it drops postmaster.pid into the data directory and removes it on a clean shutdown. That file is the lock that stops a second postmaster from starting on the same data directory, and it carries eight lines of running-instance detail, the PID, the data directory, the start time, the port, the socket directory, and so on. external_pid_file does not replace it. It asks the postmaster to write a second, much smaller file somewhere else.

The parameter is a string, empty by default, settable only at server start. Give it a path and the postmaster creates a file there at startup containing just the PID, one line and nothing more, and removes it when the server stops cleanly. The reason you would want a stripped-down copy in a location of your choosing is that the rest of the operating system has opinions about where a service’s PID file lives. The Filesystem Hierarchy Standard says it belongs under /run (historically /var/run), not buried in a database’s data directory, and init scripts and cluster resource managers go looking for it there. Setting external_pid_file = '/run/postgresql/postgresql.pid' puts it where they expect.

The one thing that reliably bites is permissions. The postmaster writes this file as the postgres user, so the directory you point it at has to be writable by that user. Aim it at a path postgres cannot write and startup fails at the moment it tries to create the file. On packaged installations the conventional /run/postgresql directory is set up with the right ownership for you; a custom location is on you to get right.

How much any of this matters now depends on how your server is supervised. Under systemd, which tracks a service’s main process directly through its cgroup, a PID file on disk is largely redundant, and many modern setups never set external_pid_file at all. Where it still earns its place is clusterware, the Pacemaker and Red Hat Cluster resource agents that predate that model and still expect a PID file at a known path. It’s just a small piece of glue between PostgreSQL and whatever is responsible for starting and stopping it. Leave it unset unless something outside the database is asking for it, and when it does, point it somewhere postgres can write.