// notes
PostgreSQL: connection refused — causes and fix
Connection refused and no pg_hba.conf entry look alike but live on different layers. I diagnose the causes and fix PostgreSQL locally or in Docker
PostgreSQL refuses connections: psql returns Connection refused or FATAL: no pg_hba.conf entry. The two errors live on different layers and need different fixes. Below: how a couple of commands tell whether the service is down, listening on the wrong address, missing a published port in Docker, or rejecting the client in pg_hba.conf, and what to do about each
Symptom
The client fails at the network layer when nothing listens on the port
$ psql -h 127.0.0.1 -p 5432 -U app -d appdb
psql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?The other error means the server answered but closed the connection under its own rules
$ psql "postgresql://app@db.example.com:5432/appdb"
psql: error: connection to server at "db.example.com" (203.0.113.10), port 5432 failed:
FATAL: no pg_hba.conf entry for host "203.0.113.10", user "app", database "appdb", no encryptionThe first error is the network layer: the packet arrived, no listener. The second is authentication: the server is reachable, but pg_hba.conf does not let this user in. Mixing them up is costly: editing pg_hba.conf will not start a stopped process, and restarting the service will not change authentication rules
Diagnosis
The first command is pg_isready. It probes the port and returns an exit code: 0 — accepting connections, 1 — rejecting, 2 — no response, 3 — no attempt made (invalid connection parameters). If the tool is missing, see Linux: command not found
$ pg_isready -h 127.0.0.1 -p 5432
127.0.0.1:5432 - accepting connections
$ echo $?
0Next, look at who listens on which address
$ ss -lntp | grep 5432
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=1234,fd=6))127.0.0.1:5432 means loopback only, 0.0.0.0:5432 means all interfaces, and empty output means nobody listens. The Unix socket shows up separately
$ ss -lxnp | grep PGSQL
u_str LISTEN 0 244 /var/run/postgresql/.s.PGSQL.5432 12345 * 0 users:(("postgres",pid=1234,fd=6))Service state depends on the installation. On Debian and Ubuntu the postgresql.service meta-unit can be active (exited) while the real cluster is down, so check the cluster itself
$ systemctl status postgresql --no-pager
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; preset: enabled)
Active: active (exited) since ...
$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
16 main 5432 down postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.logFor builds without systemd, pg_ctl reports the status
$ pg_ctl status -D "$PGDATA"
pg_ctl: server is running (PID: 1234)In Docker, check the published ports and the logs. An empty PORTS column means the port is not published
$ docker ps
CONTAINER ID IMAGE ... PORTS NAMES
a1b2c3d4e5f6 postgres:16 ... 0.0.0.0:5432->5432/tcp db
$ docker logs --tail 50 db
PostgreSQL init process complete; ready for start up.
LOG: listening on IPv4 address "0.0.0.0", port 5432
LOG: database system is ready to accept connectionsIf the server accepts connections from inside the container but the host gets Connection refused, the problem is port publishing, not PostgreSQL
$ docker exec -it db pg_isready
/var/run/postgresql:5432 - accepting connectionsCause
Several causes exist, and each one is checked separately
- The process is not running or crashed at startup: the port is taken,
PGDATAis unavailable, the config is broken, data directory permissions are wrong listen_addresses = 'localhost'inpostgresql.conf: the server listens on127.0.0.1only and rejects remote clients- The port is taken or wrong: another cluster or version listens on 5432 while the client goes to 5433
- The port is not published in Docker: the container runs without
-p 5432:5432, or publishing is bound to127.0.0.1only - A container connects to
localhost: there is no host PostgreSQL there, use the service name or the host gateway - A firewall drops packets: that is usually a timeout, not a refusal, but it is worth checking
pg_hba.confhas no matching line for the address, user, or database
Connection refusedis a TCP RST from the kernel: the packet reached the host, but nothing listens on the port. A firewall usually drops the packet, and the client sees a timeout. When the error is exactlyConnection refused, filtering is not the culprit, the missing listener is
More on pg_hba.conf. It holds rules: connection type, database, user, address, and authentication method. If no line matches, the server answers FATAL: no pg_hba.conf entry. Order matters: the first match wins
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host appdb app 127.0.0.1/32 scram-sha-256
host appdb app 10.0.0.0/8 scram-sha-256
host all all ::1/128 scram-sha-256local is the Unix socket, host is TCP, hostssl is TLS only. peer compares the OS user name, scram-sha-256 asks for a password. Lines are read top to bottom and the first match wins
Fix
Let us go through the causes
The service is not running
Start the cluster and read the journal. Substitute your own cluster name
$ sudo systemctl start postgresql@16-main
$ systemctl status postgresql@16-main --no-pager
$ journalctl -u postgresql@16-main -n 50 --no-pagerIf startup fails, the log has the reason. The data directory must belong to the postgres user and be mode 700
$ sudo ls -ld /var/lib/postgresql/16/main
drwx------ 3 postgres postgres 4096 ... /var/lib/postgresql/16/mainThe server listens on localhost only
Edit listen_addresses in postgresql.conf. The parameter is read at startup, so it needs a restart, not a reload
# /etc/postgresql/16/main/postgresql.conf
listen_addresses = '*'$ sudo systemctl restart postgresql@16-mainAfter the restart ss -lntp shows 0.0.0.0:5432 or [::]:5432. Never expose the server to the internet without a firewall and a strong password
The port is taken or wrong
The port the server itself listens on is visible in psql
$ psql -h 127.0.0.1 -p 5432 -d postgres -c 'show port;'
port
------
5432Who holds the port and where the client goes
$ sudo ss -lntp 'sport = :5432'
$ sudo lsof -iTCP:5432 -sTCP:LISTENIf the database is on 5433, pass the port explicitly: psql -p 5433. Change the connection string in the app where the host is set
The port is not published in Docker
The port is published when the container is created and cannot be changed on the fly
$ docker run -d --name db -p 127.0.0.1:5432:5432 \
-e POSTGRES_PASSWORD='<password>' postgres:16In Compose the same is described by the ports section
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"
environment:
POSTGRES_PASSWORD: "<password>"-p 127.0.0.1:5432:5432 publishes the port on the host loopback only, -p 5432:5432 on all interfaces. For local development the first one is enough
Connecting to localhost from a container
Inside a container localhost points at the container itself. If the database is a neighbouring Compose service, use its name
# instead of postgresql://app@localhost:5432/appdb
postgresql://app@db:5432/appdbIf the database is on the host, add the gateway on Linux: docker run --add-host=host.docker.internal:host-gateway. Keeping the database as a service in the same Compose network is still more reliable
Firewall
Remote connections are filtered on the host and in the cloud panel. Open the port narrowly
$ sudo ufw allow from 203.0.113.0/24 to any port 5432 proto tcp
$ sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port port="5432" protocol="tcp" accept'Remember the symptom: Connection refused is not a firewall. First confirm the port is listening, only then touch the filters
pg_hba rules
Add a line for your user and reload the rules. pg_hba.conf applies without a restart
# /etc/postgresql/16/main/pg_hba.conf
host appdb app 10.0.0.0/8 scram-sha-256$ sudo -u postgres psql -c 'select pg_reload_conf();'
pg_reload_conf
----------------
tThe trust method lets anyone in without a password and is acceptable for local development only. peer works for the Unix socket and compares the OS user name
Setting up a database from scratch is covered separately: PostgreSQL: install and setup
Verify
Check by layers: first the port, then authentication
$ pg_isready -h 127.0.0.1 -p 5432
127.0.0.1:5432 - accepting connections
$ psql "postgresql://app@127.0.0.1:5432/appdb" -c 'select 1;'
?column?
----------
1
(1 row)From the container
$ docker exec -it db pg_isready
/var/run/postgresql:5432 - accepting connectionsAnd a final check with explicit parameters
$ psql -h 127.0.0.1 -p 5432 -U app -d appdb -c '\conninfo'
You are connected to database "appdb" as user "app" on host "127.0.0.1" at port "5432".If select 1 returns a row but the app still fails, the network is not the issue: check the connection string and the service environment variables
Pitfalls
- A Unix socket and TCP are different channels.
psqlwithout-huses/var/run/postgresql, whilepsql -h localhostgoes over TCP. The server can accept the socket and refuse over the network:localandhostrules are independent - `peer` for local connections. On Debian and Ubuntu
local all all peercompares the OS user name.psql -U postgresas a regular user givesPeer authentication failed; usesudo -u postgres psql - `localhost` may resolve to IPv6. If the server listens on
127.0.0.1only andlocalhostresolves to::1, the client getsConnection refused. Pass127.0.0.1explicitly - `PGDATA` selects the cluster. With a manual start, a wrong path brings up an empty or foreign cluster. Check it:
pg_ctl status -D "$PGDATA" - Several clusters on one port. On Debian and Ubuntu
pg_lsclustersshows them; whoever grabbed the port first wins - `pg_hba.conf` is read top to bottom. A
host all all 0.0.0.0/0 trustline above yours nullifies the rest
Takeaway
Connection refused is fixed by checking the listener: the process is down, the address is wrong, the port is not published. no pg_hba.conf entry is the rules layer, and the server is reachable over the network. Diagnose in this order: pg_isready, then ss -lntp, then pg_hba.conf
If the database has to run under load without surprises rather than merely on a laptop, PostgreSQL consulting and backend development can help
Related notes
PostgreSQL installation and setup
Install PostgreSQL with apt or Docker: cluster, initdb, pg_hba, roles and load-ready settings. Step-by-step commands with a check after every change
pkill -f kills itself: the pattern matches the command line
pkill -f and pgrep -f match the pattern against the whole command line, including the bash -c wrapper. Why the command kills itself and which filters help
Nix on CachyOS: install impossible — run it rootless
Nix will not install when the root is read-only, sudo is broken and /nix is baked into ELF. Three limits and a rootless run with mount namespace and chroot