Tall ship with billowed sails docked at a medieval European harbor, crew members on deck and rigging, spectators on the wharf, cityscape with church spires in background.

Most people first read this parameter’s value in an error message that has no TCP in it:

1psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
2 Is the server running locally and accepting connections on that socket?

The 5432 on the end of that file name is port. The documentation calls the parameter “the TCP port the server listens on,” which is true, and about half of what it does.

It is an integer from 1 to 65535, and the default is 5432. The default is compiled in (--with-pgport, if you build your own), and it is compiled into libpq too, which is why a client and a server that have never been introduced can find each other. The context is postmaster, so changing it means a restart. A server gets exactly one: every address in listen_addresses and every directory in unix_socket_directories uses the same number, and everything that talks to the server comes in through it. That includes replication, and it includes query cancellation, which arrives as a new connection. There is no second port for administrators. (5432 is PostgreSQL’s by registration; the IANA entry lists Tom Lane as the assignee.)

What the number names

The postmaster uses port in four places at startup. It binds a TCP socket on that port for each address in listen_addresses; what happens when a bind fails is in that post. It creates a socket file named .s.PGSQL.<port>, and a lock file named .s.PGSQL.<port>.lock, in each socket directory. It writes the number as the fourth line of postmaster.pid, with the socket directory on the fifth, which is the fastest way to find out where a server is when you cannot connect to it to ask. And if bonjour is on, this is the port it advertises.

Through PostgreSQL 12 there was a fifth. The search for a System V shared memory key started at the port times 1000, plus one, and that is the 0x0052e2c1 in ipcs output from an old server: 5432001. (Semaphore keys were derived from the port the same way.) PostgreSQL 13 switched both to the data directory’s inode number.

So the port is the server’s local name as much as its TCP address, and a server with listen_addresses = '' needs it as much as any other. It also means that “one server per port” is the wrong rule. TCP ports are unique per address (with * counting as every address), and socket files are unique per directory. Two servers that share neither can both be on 5432. These two commands were run on the same host, one after the other:

1$ psql -XAtc "SELECT current_setting('port'), current_setting('server_version')"
25432|16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
3$ psql -h localhost -XAtc "SELECT current_setting('port'), current_setting('server_version')"
45432|18.6 (Ubuntu 18.6-1.pgdg24.04+2)

The first server has no TCP sockets and keeps its Unix socket in /var/run/postgresql. The second listens on localhost and keeps its socket somewhere else. Neither logged a complaint, because neither has anything to complain about. Two servers bound to different addresses on a multi-homed host, each with its own socket directory, get the same result over TCP. Either way, the migrations go to one database and the queries to the other, and the port number is the one thing about them that matches.

(The under-1024 restriction belongs to TCP as well. On Linux with default settings an unprivileged process cannot bind a low port, and PostgreSQL will not run as root, so port = 543 fails with could not bind IPv4 address, Permission denied. Take away the TCP addresses and the same server starts, listening on .s.PGSQL.543.)

Where the number comes from

There are four sources, each overriding the one before it: the compiled default, PGPORT in the postmaster’s environment (the variable libpq reads; the server reads it too), the configuration files, and -p on the postgres command line. pg_settings.source reports which one won, as default, environment variable, configuration file, or command line.

Packaging decides more of this than you might expect. On Debian and Ubuntu, pg_createcluster writes an explicit port line into each new cluster’s postgresql.conf, taking the first free number from 5432 upward, so the second cluster on a machine is on 5433 whether or not anyone asked. With several clusters present and nothing else configured, a bare psql goes to whichever one holds 5432, and pg_upgradecluster swaps the two numbers when it finishes so that the new cluster inherits the old one’s clients. Plain pg_upgrade goes the other way: unless told otherwise it runs both clusters on 50432, so that nothing configured for 5432 can wander in halfway through.

The command line is the one to watch. Patroni takes the port from postgresql.listen in its own configuration and passes it to postgres as an argument, and anything else that starts the server with -p behaves the same way. On an 18.6 server started with -p 5433:

1postgres=# ALTER SYSTEM SET port = 6543;
2ALTER SYSTEM
3postgres=# SELECT pg_reload_conf();
4 pg_reload_conf
5----------------
6 t
7(1 row)
8
9postgres=# SELECT setting, source, pending_restart FROM pg_settings WHERE name = 'port';
10 setting | source | pending_restart
11---------+--------------+-----------------
12 5433 | command line | t
13(1 row)

pending_restart says a restart will apply the change. After the restart:

1 setting | source | pending_restart
2---------+--------------+-----------------
3 5433 | command line | f
4(1 row)

The flag has cleared, the port has not moved, and the startup log does not mention it. Then something reloads the configuration, the flag goes back to t, and the log repeats what it said the first time:

1LOG: parameter "port" cannot be changed without restarting the server

The server will go on asking for that restart for as long as the line sits in postgresql.auto.conf, and no restart will satisfy it. pg_file_settings is the view that tells the truth, with applied = f and setting could not be applied against the line. (Any restart-only parameter given on the command line behaves this way. A reloadable one is quieter: pg_file_settings reports applied = t, and the command-line value stays where it was.)

Then there is the number in the connection string, which may not be this parameter at all. SHOW port and inet_server_port() report the port the postmaster bound, and the server has no idea what the client dialed. Put something in between that listens on a different number (a container started with -p 5433:5432, an SSH tunnel, PgBouncer on its customary 6432) and the server’s answer stops matching the connection string. Through a forwarder on port 6000 in front of a default server, SHOW port said 5432 and psql’s \conninfo said 6000. Both were right. When someone asks which port the database is on, find out which of the two they mean.

Before you change it

The restart is the cheap part. The old number is written down in every connection string, in primary_conninfo on every standby, in the connection string of every subscription that points here and the options of every foreign server that does, in pgbouncer.ini, in pgBackRest’s pg1-port, in the monitoring agent’s configuration, and in a firewall rule. On RHEL-family systems with SELinux enforcing, the policy only lets PostgreSQL bind ports labeled postgresql_port_t. 5432 already is; a new port needs semanage port -a -t postgresql_port_t -p tcp <port> before the restart, or the bind is refused. On RDS the port is an instance setting, and modifying it reboots the instance immediately, whatever you chose for “apply immediately.”

Two are easy to miss. ~/.pgpass matches on port, so an entry written as db1:5432:*:app:... quietly stops applying, and a client with no other source for the password fails with fe_sendauth: no password supplied. (Put * in the port field if you would rather not find out this way.) And local connections over the Unix socket break along with the TCP ones, because the file they were opening has a new name. That is the error at the top of this post.

None of which is a reason to move. The argument people make for leaving 5432 is that scanners know it, and on a server that is reachable from the internet, a different number does cut the failed logins in the log. It protects nothing; anything that can try one port can try all of them. Fix the part where the server is reachable from the internet. Leave the main server on a host at 5432, where every client, driver, and tool already expects it, and spend the other numbers on what they are for, which is the second server on the same machine.