PostgreSQL Connection Refused: How to Fix It Once and for All

PostgreSQL Connection Refused: How to Fix It Once and for All

If you’re staring at a psql: could not connect to server: Connection refused error in your terminal, take a breath. You’re in good company — this is one of the most frequently encountered PostgreSQL errors, and the good news is that almost every instance has a traceable root cause.

In this guide, I’ll walk you through exactly how to diagnose and resolve the “postgresql connection refused” error, covering everything from the obvious (the service isn’t running) to the subtle (IPv6/IPv4 mismatches, stale UNIX sockets, and container networking gotchas). By the end, you’ll have a repeatable troubleshooting playbook you can apply to any environment — local dev, staging, or production.


Understanding the “Connection Refused” Error

Before diving into fixes, it’s worth understanding what “connection refused” actually means at the network layer. This isn’t a generic failure message — it has a specific meaning.

When your client attempts a TCP connection to PostgreSQL (default port 5432), one of three things typically happens:

  1. Connection accepted — the server is listening and accepts the handshake.
  2. Connection timed out — packets are being dropped (usually a firewall or routing issue).
  3. Connection refused — the host responded, but no process is listening on that port.

The third scenario is what we’re dealing with here. The target machine is reachable, the network path works, but nothing is accepting connections at the address and port combination your client tried. This distinction matters because it eliminates entire categories of problems (DNS, routing, ISP-level blocks) and points us toward server-side configuration.

The classic error message looks like this:

psql: error: connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections on that port?

Or, in older versions:

psql: could not connect to server: Connection refused
        Is the server running on that host and accepting TCP/IP connections on that port?

Both messages point to the same underlying condition. Let’s work through the causes from most common to edge case.


Quick Diagnostic Checklist

When production is down, you don’t have time to read a 3,000-word guide sequentially. Here’s the executive summary — work through these in order:

  1. Is the PostgreSQL service actually running?
  2. Is it listening on the interface you’re connecting to?
  3. Is it listening on the port you’re targeting?
  4. Does pg_hba.conf allow your client IP?
  5. Is a firewall blocking traffic?
  6. Are you inside a container with networking misconfigured?
  7. Are you hitting an IPv6-vs-IPv4 mismatch?

Most cases resolve at step 1 or 2. If you get through all seven without resolution, you’re dealing with something genuinely unusual — and we’ll cover those cases too.


Step 1: Verify PostgreSQL Is Actually Running

This is the #1 cause of connection refused errors, especially in development environments. People assume the server is running because they installed it, but installations don’t always start the service automatically — and reboots, crashes, or package updates can stop a previously-running instance.

Check the Service Status

On systemd-based Linux distributions (Ubuntu 20.04+, Debian 11+, RHEL 8+, Fedora):

sudo systemctl status postgresql

You’re looking for active (running) in the output. If you see inactive or failed, that’s your culprit.

On macOS with Homebrew:

brew services list

Look for postgresql@14 (or whatever version you installed) and confirm the status is started.

On Windows, open PowerShell as Administrator:

Get-Service -Name postgresql*

Or use services.msc if you prefer the GUI.

Start the Service

If it’s stopped, start it:

# Linux
sudo systemctl start postgresql
sudo systemctl enable postgresql  # auto-start on boot

# macOS
brew services start postgresql@14

# Windows (PowerShell as Admin)
Start-Service -Name postgresql-x64-14

What If It Fails to Start?

If systemctl start postgresql returns a failure, dig into the logs:

sudo journalctl -u postgresql -n 50 --no-pager

Common reasons PostgreSQL won’t start:

  • Corrupt pg_xlog / WAL files after a hard crash.
  • Port 5432 already in use by another process.
  • Permissions issues on the data directory (often /var/lib/postgresql/<version>/main).
  • Insufficient disk space — Postgres won’t start if the partition holding the data directory is full.
  • Mismatched postgresql.conf after an upgrade (a directive that existed in version 13 but was removed in 15, for instance).

For the port-in-use scenario, check what’s holding 5432:

sudo lsof -i :5432
# or
sudo ss -tlnp | grep 5432

If you see a stale postgres process from a crashed instance, kill it:

sudo kill -9 <PID>
sudo systemctl start postgresql

I once spent two hours chasing this on a client’s staging server only to discover that a Docker container had bound to 5432 on the host network, and the native PostgreSQL service couldn’t acquire the port. Always check for port conflicts before going deeper.


Step 2: Confirm the Listen Address and Port

Even when PostgreSQL is running, it may only be accepting connections on localhost (127.0.0.1), which means any client connecting via a hostname, public IP, or even the machine’s LAN address will be refused.

Inspect the Active Listeners

sudo ss -tlnp | grep postgres

A healthy local-only setup shows:

LISTEN  0  244  127.0.0.1:5432  0.0.0.0:*  users:(("postgres",pid=1234,fd=5))

A server accepting remote connections shows:

LISTEN  0  244  0.0.0.0:5432  0.0.0.0:*  users:(("postgres",pid=1234,fd=5))

If you don’t see your target IP in the listen address, you need to update postgresql.conf.

Update listen_addresses

Locate your postgresql.conf file:

sudo -u postgres psql -c "SHOW config_file;"

Open it and find the listen_addresses directive:

sudo nano /etc/postgresql/15/main/postgresql.conf

Change from:

#listen_addresses = 'localhost'

To:

listen_addresses = '*'

Using '*' listens on all interfaces. For tighter security, specify exact IPs:

listen_addresses = 'localhost,192.168.1.100'

Then restart:

sudo systemctl restart postgresql

Confirm the Port

While you’re in postgresql.conf, double-check the port setting:

port = 5432

If your application expects 5432 but PostgreSQL is running on, say, 5433 (common when multiple versions are installed side by side), you’ll see connection refused on 5432 even though Postgres is technically healthy. This happens frequently after upgrading from PostgreSQL 13 to 15 on systems where both versions were installed simultaneously.


Step 3: Review pg_hba.conf Authentication Rules

Here’s a subtle trap: pg_hba.conf (Host-Based Authentication) controls who can connect. If it doesn’t permit your client, you typically get an authentication error rather than “connection refused” — but in some edge cases (particularly when rules are misordered or use reject), the behavior can mimic a refused connection.

Locate the file:

sudo -u postgres psql -c "SHOW hba_file;"

A typical development setup that allows local and LAN connections:

# TYPE  DATABASE  USER  ADDRESS       METHOD
local   all       all                 trust
host    all       all   127.0.0.1/32  scram-sha-256
host    all       all   ::1/128       scram-sha-256
host    all       all   192.168.0.0/16  scram-sha-256

Key points:

  • Order matters. PostgreSQL evaluates rules top-to-bottom and uses the first match. A reject rule above an allow rule blocks access silently.
  • scram-sha-256 is the recommended method in PostgreSQL 15+. Older setups may use md5, which is deprecated.
  • trust allows passwordless connections — fine for local dev, dangerous in production.

After editing pg_hba.conf, reload (no restart needed):

sudo systemctl reload postgresql

Step 4: Check Firewall Rules

If PostgreSQL is running and listening on the correct interface, but external clients still get “connection refused,” a firewall is the likely suspect. Note: a strict firewall can also cause timeouts rather than refusals, depending on whether it sends RST packets or silently drops traffic.

UFW (Ubuntu / Debian)

sudo ufw status verbose
sudo ufw allow 5432/tcp
# or restrict to a specific subnet:
sudo ufw allow from 192.168.1.0/24 to any port 5432

firewalld (RHEL / CentOS / Fedora)

sudo firewall-cmd --permanent --add-service=postgresql
sudo firewall-cmd --reload

iptables

sudo iptables -L -n | grep 5432
sudo iptables -A INPUT -p tcp --dport 5432 -j ACCEPT

Cloud Provider Security Groups

If you’re on AWS EC2, GCP Compute Engine, or Azure VMs, the cloud-level security group/firewall must also allow inbound TCP 5432. I’ve seen developers spend hours debugging a perfectly configured PostgreSQL instance only to discover the AWS Security Group blocked the port. Check this before anything else in cloud environments.


Step 5: Docker and Container Networking Issues

Running PostgreSQL in Docker introduces a whole new class of “connection refused” scenarios. Let’s cover the common ones.

The Container Isn’t Exposing the Port

Your docker run or docker-compose.yml must publish the port:

docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  postgres:15

Without -p 5432:5432, the container listens on 5432 internally but nothing on the host forwards to it.

In docker-compose.yml:

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"

Connecting Between Containers

If your app runs in one container and PostgreSQL in another, do not use localhost. Containers have their own network namespaces.

With Docker Compose, services can reach each other by service name:

# Python example (psycopg2 / psycopg3)
import psycopg

conn = psycopg.connect(
    host="postgres",  # the service name from docker-compose.yml
    port=5432,
    dbname="myapp",
    user="postgres",
    password="secret"
)

Using localhost here will give you connection refused because, inside the app container, nothing is listening on localhost:5432 — Postgres lives in a different container.

The Container Started but Postgres Hasn’t Initialized Yet

PostgreSQL takes a few seconds to initialize on first run (creating the cluster, setting up users). If your app boots faster than Postgres, you’ll see transient connection refused errors. The fix is a healthcheck with a dependency wait:

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    image: myapp:latest
    depends_on:
      postgres:
        condition: service_healthy

This pattern has saved me from writing custom retry loops in every application that depends on a database.


Step 6: Edge Cases and Unusual Causes

If you’ve made it this far and the error persists, you’re likely dealing with one of the following.

UNIX Socket vs TCP Mismatch

PostgreSQL supports two connection types: TCP/IP sockets and UNIX domain sockets. Some clients default to UNIX sockets, which require a socket file (typically /var/run/postgresql/.s.PGSQL.5432 or /tmp/.s.PGSQL.5432).

If the socket file is missing or in a different location than expected:

# Find the socket
sudo find / -name ".s.PGSQL.*" 2>/dev/null

# Check the configured socket directory
sudo -u postgres psql -c "SHOW unix_socket_directories;"

Force a TCP connection explicitly:

psql -h 127.0.0.1 -U postgres -p 5432

Using 127.0.0.1 instead of localhost forces TCP in most client libraries, bypassing UNIX socket resolution entirely.

IPv6 Resolution Quirks

Modern systems may resolve localhost to ::1 (IPv6 loopback) before 127.0.0.1 (IPv4). If PostgreSQL is only listening on IPv4, the IPv6 connection attempt gets refused.

Check /etc/hosts:

cat /etc/hosts

If you see:

::1     localhost
127.0.0.1   localhost

Your client might be trying ::1 first. Solutions:

  1. Configure PostgreSQL to listen on both: listen_addresses = 'localhost,::1'
  2. Connect via 127.0.0.1 explicitly instead of localhost.
  3. Remove or comment the ::1 line in /etc/hosts if you don’t use IPv6 locally.

SELinux Blocking Connections

On RHEL, CentOS, and Fedora with SELinux in enforcing mode, the postgresql SELinux policy may block network connections even when the firewall and PostgreSQL config are correct.

Check SELinux status:

getenforce
# Output: Enforcing

Check the relevant boolean:

getsebool postgresql_can_connect

If it’s off, enable it:

sudo setsebool -P postgresql_can_connect on

To allow PostgreSQL to accept remote connections:

sudo setsebool -P httpd_can_network_connect_db on

Stale .pid File After a Crash

If PostgreSQL crashed hard (power loss, OOM killer), it may leave behind a postmaster.pid file that prevents restart:

sudo ls -la /var/lib/postgresql/15/main/postmaster.pid

If you’ve confirmed no postgres process is running, remove the stale file:

sudo rm /var/lib/postgresql/15/main/postmaster.pid
sudo systemctl start postgresql

Warning: Only do this if you’ve verified via ps aux | grep postgres that no process is actually running. Removing the PID file of a live server will cause data corruption.

Connection Pool Exhaustion (Refused-Like Behavior)

Strictly speaking, max-connections exhaustion produces a different error (FATAL: sorry, too many clients already), but if there’s a proxy or pooler (PgBouncer, pgcat) in front, you may see “connection refused” at the pooler level when its backlog is full.

Check active connections:

SELECT count(*) FROM pg_stat_activity;

Compare to:

SHOW max_connections;

If you’re near the limit, either increase max_connections in postgresql.conf or investigate connection leaks in your application.


Client-Side Configuration Issues

Sometimes the server is perfectly fine and the client is misconfigured. Common culprits:

Wrong Connection String in Environment Variables

# Check what your app actually sees
echo $DATABASE_URL

A typo like postgrsql:// instead of postgresql://, or localhost:543 instead of localhost:5432, will produce connection refused.

SSL/TLS Mismatch

If PostgreSQL requires SSL (ssl = on with hostssl rules in pg_hba.conf) but your client doesn’t support or enable SSL, you may see odd connection failures. Most modern drivers handle this gracefully, but older ones don’t.

Python example with explicit SSL:

“`python
import psycopg

conn = psycopg.connect(
host=”db.example.com”,
port=5432

Leave a Reply

Your email address will not be published. Required fields are marked *