5432/TCP is PostgreSQL’s default port, assigned to it by IANA and unchanged since the beginning. When a connection to it fails, the message tells you which of two files to open: connection refused points at postgresql.conf, and no pg_hba.conf entry points at exactly what it says.
That distinction is the whole of PostgreSQL connection debugging and it saves an enormous amount of time. Postgres has two independent gates — one deciding which network interfaces it listens on, another deciding which client, user, and database combinations are allowed to authenticate. They fail differently, and reading which failure you got tells you which file to edit.
Table of contents
- Confirming the server is listening
- postgresql.conf: which interfaces
- pg_hba.conf: who may authenticate
- Reading the error messages
- Connecting, and the URL form
- Connection limits and pooling
- How this fits the rest of the stack
- FAQ
Confirming the server is listening
sudo ss -tlnp | grep 5432
sudo lsof -i :5432
pg_isready -h 127.0.0.1 -p 5432
pg_isready is the right first command and is underused. It returns accepting connections, rejecting connections, or no response, with a matching exit code, and it does not need credentials. That makes it perfect for a health check and for answering the first question without any authentication noise in the way.
As with MySQL, read the bind address in the ss output. 127.0.0.1:5432 accepts local connections only. 0.0.0.0:5432 accepts from anywhere the firewall permits.
One PostgreSQL-specific wrinkle: multiple clusters on one host. Debian and Ubuntu package Postgres so that several major versions can coexist, each with its own port. Version 15 might be on 5432 and version 16 on 5433, and connecting to the wrong one gives you an empty database that looks like data loss. pg_lsclusters lists them with their ports and status, and it is the first thing to run when a database appears to have lost its tables.
postgresql.conf: which interfaces
The listen_addresses setting controls where the server binds. It defaults to localhost, which is a sensible default and the reason remote connections fail out of the box.
# postgresql.conf
listen_addresses = 'localhost' # default, local only
listen_addresses = '10.0.1.15' # one private interface
listen_addresses = '*' # every interface
port = 5432
max_connections = 100
Find the file with a query rather than guessing at paths, since it varies by distribution and version:
SHOW config_file;
SHOW hba_file;
SHOW listen_addresses;
SHOW port;
Changing listen_addresses or port needs a full restart, not a reload — they are startup-only settings. Most other things, including everything in pg_hba.conf, take effect with SELECT pg_reload_conf(); or systemctl reload postgresql.
Set listen_addresses to a specific private address rather than * whenever you can. It is one character more effort and it means a misconfigured firewall does not immediately expose the database.
pg_hba.conf: who may authenticate
This is the file with no equivalent in MySQL, and it is where most remaining confusion lives. pg_hba.conf is a list of rules matched top to bottom; the first matching line wins, and if nothing matches the connection is rejected.
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
host appdb appuser 10.0.1.0/24 scram-sha-256
hostssl appdb appuser 0.0.0.0/0 scram-sha-256
Reading the columns matters. local means Unix socket connections, host means TCP with or without TLS, and hostssl means TCP with TLS required. peer authenticates by matching the operating system username, which is why sudo -u postgres psql works with no password and the same command as another user does not.
The error when nothing matches is unusually clear, and it names the exact user, database, and address you need to write a rule for:
FATAL: no pg_hba.conf entry for host "10.0.1.42",
user "appuser", database "appdb", no encryption
Note the trailing no encryption in that message. If your rule is hostssl and the client connected without TLS, the rule does not match and you get this error even though the line looks correct. Add sslmode=require on the client side, and the same rule starts matching.
First match wins, so ordering is load-bearing. A broad host all all 0.0.0.0/0 trust line near the top makes every careful rule beneath it irrelevant. And trust means no password at all — it belongs in a throwaway container and nowhere else.
Reading the error messages
could not connect to server: Connection refused— nothing is listening at that address and port. Server down,listen_addressestoo narrow, or a firewall. Nothing to do with credentials.no pg_hba.conf entry for host ...— the server heard you and no rule matched. Editpg_hba.conf, then reload. This is progress: the network is fine.password authentication failed for user ...— a rule matched, the password is wrong. Or the role does not exist.FATAL: database "appdb" does not exist— connected and authenticated fine. The database name is wrong, or you are on a different cluster than you think.FATAL: sorry, too many clients already—max_connectionsis exhausted. An application pooling problem far more often than a load problem.- Connection hangs with no error — usually a firewall dropping packets silently rather than rejecting them. A rejection is fast; a drop times out.
That last distinction is worth internalising. Fast failure means something answered. Slow failure means something ate the packet. It tells you whether to look at the database or the network before you have looked at anything else.
Connecting, and the URL form
psql -h 10.0.1.15 -p 5432 -U appuser -d appdb
psql "postgresql://appuser:[email protected]:5432/appdb?sslmode=require"
# environment variables, which most tooling reads
export PGHOST=10.0.1.15 PGPORT=5432 PGUSER=appuser PGDATABASE=appdb
The connection URL form is what most frameworks want in a DATABASE_URL environment variable. Two things about it are worth being deliberate: the password in a URL ends up in shell history and process listings, so prefer PGPASSWORD from a secret store or a ~/.pggass file; and sslmode defaults to prefer, which silently falls back to unencrypted if TLS is unavailable.
For anything crossing a network, set sslmode=require at minimum. verify-full is better still, because require encrypts without verifying who you are talking to — it stops passive eavesdropping and not an active attacker.
Connection limits and pooling
PostgreSQL’s default max_connections of 100 is lower than people expect, and each connection is a separate backend process with its own memory. That design is why a pooler is standard practice rather than an optimisation.
SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
SELECT pid, state, query_start, left(query, 60)
FROM pg_stat_activity WHERE state <> 'idle' ORDER BY query_start;
That grouped count is the diagnostic worth running first. A large number of idle in transaction connections is a specific bug, not a capacity problem — application code opened a transaction and did not commit or roll back, so each of those is holding locks and a connection indefinitely. Raising max_connections makes it worse by giving the leak more room.
For genuine concurrency, PgBouncer in transaction mode lets hundreds of client connections share a few dozen server ones. Serverless and autoscaling architectures make this close to mandatory, since each new instance arrives with its own pool and the total climbs faster than anyone tracks.
How this fits the rest of the stack
Both files above exist because you are operating the server. On a managed instance the equivalent decisions are already made in the safe direction: the database is reachable from your services over private networking rather than from the internet, TLS is on, connection limits are a documented figure for the plan, and backups are configuration rather than a script. RunxBuild runs managed Postgres and MySQL on that model — databases on RunxBuild covers connecting and users, and database connection limits on RunxBuild covers the number you will eventually need. When you are comparing that against a Postgres you administer yourself, the RunxBuild hosting calculator breaks out the database, the service, storage, and bandwidth as separate line items rather than one figure.
Useful related references:
- Postgres Port 5432: What to Check Before Deploying
- PostgreSQL Default Port 5432: The Number, the History
- PostgreSQL Port: 5432 (Default), How to Change, and Multi-Version Setup
- Databases on RunxBuild
FAQ
What is port 5432 used for?
It is the default TCP port for PostgreSQL, assigned by IANA. Clients like psql, pgAdmin, DBeaver, and every language driver connect to it. On Debian-family systems running multiple major versions, the second cluster usually sits on 5433 — check with pg_lsclusters.
Why can I not connect to PostgreSQL on port 5432?
Read the exact error. Connection refused means nothing is listening — check the service and listen_addresses in postgresql.conf. no pg_hba.conf entry means the server heard you but no authentication rule matched, which is a pg_hba.conf edit followed by a reload.
What is the difference between listen_addresses and pg_hba.conf?
listen_addresses decides which network interfaces the server binds to and needs a restart to change. pg_hba.conf decides which combinations of client address, user, and database may authenticate, and only needs a reload. They fail with different error messages, which tells you which one to edit.
What does no pg_hba.conf entry with no encryption mean?
Your matching rule is probably hostssl, which only matches TLS connections, while the client connected without TLS. Add sslmode=require to the client connection string and the rule will match. Alternatively change the rule to host, though requiring TLS is the better setting.
Why does PostgreSQL run out of connections so easily?
The default max_connections is 100 and each connection is a separate backend process with its own memory. Check SELECT state, count(*) FROM pg_stat_activity GROUP BY state — a pile of idle in transaction entries is an application bug, not a capacity issue. Use PgBouncer rather than raising the limit.