Skip to content
← All notes

// 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

Published: 2026-10-095 min read
PostgreSQLconnection-refusedDockerpg_hbaLinux

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 encryption

The 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 $?
0

Next, 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.log

For 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 connections

If 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 connections

Cause

Several causes exist, and each one is checked separately

  • The process is not running or crashed at startup: the port is taken, PGDATA is unavailable, the config is broken, data directory permissions are wrong
  • listen_addresses = 'localhost' in postgresql.conf: the server listens on 127.0.0.1 only 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 to 127.0.0.1 only
  • 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.conf has no matching line for the address, user, or database
Connection refused is 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 exactly Connection 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-256

local 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-pager

If 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/main

The 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-main

After 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
------
 5432

Who holds the port and where the client goes

$ sudo ss -lntp 'sport = :5432'
$ sudo lsof -iTCP:5432 -sTCP:LISTEN

If 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:16

In 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/appdb

If 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
----------------
 t

The 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 connections

And 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. psql without -h uses /var/run/postgresql, while psql -h localhost goes over TCP. The server can accept the socket and refuse over the network: local and host rules are independent
  • `peer` for local connections. On Debian and Ubuntu local all all peer compares the OS user name. psql -U postgres as a regular user gives Peer authentication failed; use sudo -u postgres psql
  • `localhost` may resolve to IPv6. If the server listens on 127.0.0.1 only and localhost resolves to ::1, the client gets Connection refused. Pass 127.0.0.1 explicitly
  • `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_lsclusters shows them; whoever grabbed the port first wins
  • `pg_hba.conf` is read top to bottom. A host all all 0.0.0.0/0 trust line 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

Need this solved on your project?