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:
- Connection accepted — the server is listening and accepts the handshake.
- Connection timed out — packets are being dropped (usually a firewall or routing issue).
- 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:
- Is the PostgreSQL service actually running?
- Is it listening on the interface you’re connecting to?
- Is it listening on the port you’re targeting?
- Does
pg_hba.confallow your client IP? - Is a firewall blocking traffic?
- Are you inside a container with networking misconfigured?
- 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.confafter 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
rejectrule above anallowrule blocks access silently. scram-sha-256is the recommended method in PostgreSQL 15+. Older setups may usemd5, which is deprecated.trustallows 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:
- Configure PostgreSQL to listen on both:
listen_addresses = 'localhost,::1' - Connect via
127.0.0.1explicitly instead oflocalhost. - Remove or comment the
::1line in/etc/hostsif 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