โ† Back to DevBytes

Fix PostgreSQL 'Connection refused' Error in Production: Root Cause Analysis

Understanding the PostgreSQL 'Connection refused' Error

When you attempt to connect to a PostgreSQL server and see the message Connection refused (often accompanied by psql: could not connect to server: Connection refused or Is the server running on host "..." and accepting TCP/IP connections on port 5432?), the client's TCP connection request is being actively denied. This is a low-level network error distinct from authentication failures or timeouts. It means that the operating system on the target host received the TCP SYN packet but returned a RST (reset) packet, or that no socket is bound to the specified address and port.

Root causes fall into a handful of categories:

Why This Error Matters in Production

A Connection refused error is a hard stop for any application relying on the database. In production, it can cause immediate outages: web requests fail, background jobs stall, and monitoring alerts fire. Unlike transient network blips, refusal indicates a fundamental disconnect between client expectations and server reality. Without a systematic root cause analysis, teams waste time restarting random services or blaming the network when the fix might be a one-line configuration change.

Moreover, repeated refusals can cascade: connection pools exhaust retries, circuit breakers trip, and user-facing errors multiply. Understanding the exact failure mode is critical to restoring service quickly and preventing recurrence.

Systematic Root Cause Analysis

๐Ÿš€ Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Start by narrowing down where the refusal originates. Use a checklist approach, moving from the server process outward.

1. Verify PostgreSQL is Running and Listening

On the database host, check if the postgres process exists and is bound to the expected port.

# Check service status (systemd)
systemctl status postgresql

# Or for specific version (e.g., PostgreSQL 15)
systemctl status postgresql-15

# List all PostgreSQL processes
ps aux | grep postgres

# Check listening sockets (replace 5432 if custom port)
ss -tlnp | grep 5432
netstat -tlnp | grep 5432

If no process is running, start it:

sudo systemctl start postgresql

If it fails to start, examine logs immediately:

journalctl -u postgresql --since "5 minutes ago"
# Or check PostgreSQL's own log (path varies)
tail -100 /var/log/postgresql/postgresql-15-main.log

Common start failures: bad configuration in postgresql.conf, missing data directory, disk full, or port already in use.

2. Check Listen Addresses Configuration

PostgreSQL only listens on addresses specified by listen_addresses in postgresql.conf. The default often is 'localhost' or '127.0.0.1', which rejects any remote client. Find the active configuration file:

# Locate the config file (inside psql or via pg_ctl)
psql -U postgres -c "SHOW config_file;"
# or
pg_ctl status -D /var/lib/postgresql/15/main

Edit the file (path varies by distribution) and set:

listen_addresses = '*'   # listen on all interfaces (IPv4 and IPv6)
# or specify a comma-separated list of IPs:
listen_addresses = '192.168.1.10,10.0.0.5'

After changing, restart or reload:

sudo systemctl reload postgresql
# or restart if reload not supported
sudo systemctl restart postgresql

3. Verify the Correct Port

Confirm the port PostgreSQL actually uses:

psql -U postgres -c "SHOW port;"
# or grep the config file
grep "^port" /etc/postgresql/15/main/postgresql.conf

If the client connects to a different port, adjust connection strings or change the server port. Remember to update firewall rules accordingly.

4. Firewall and Network Filter Analysis

Even if PostgreSQL is listening, a firewall can reject the TCP SYN. Check local firewall rules:

# iptables (legacy)
sudo iptables -L -n | grep 5432

# firewalld (common on RHEL/CentOS)
sudo firewall-cmd --list-all
sudo firewall-cmd --list-services

# ufw (Ubuntu)
sudo ufw status

If port 5432 (or your custom port) is not allowed, add a rule:

# firewalld example
sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --reload

# ufw
sudo ufw allow 5432/tcp

In cloud environments (AWS, GCP, Azure), check Security Groups or firewall tags attached to the instance. Ensure inbound TCP on port 5432 is permitted from the client's IP range.

5. SELinux or AppArmor Restrictions

SELinux on Red Hat derivatives can prevent PostgreSQL from binding to non-default ports or accepting remote connections. Check audit logs:

sudo ausearch -m avc -ts recent | grep postgres

Common issues: wrong port context or postgresql_t type enforcement. Temporarily test by putting SELinux in permissive mode:

sudo setenforce 0
# Test connection, then re-enable
sudo setenforce 1

If the connection works only when permissive, fix the policy:

sudo semanage port -a -t postgresql_port_t -p tcp 5432
# Or set boolean to allow remote connections
sudo setsebool -P postgresql_can_network_connect on

For AppArmor (Ubuntu), check status:

sudo aa-status | grep postgres

And review /etc/apparmor.d/ profiles.

6. Connection Limit Exhaustion

When max_connections is reached, PostgreSQL rejects new connections with a specific error message (FATAL: sorry, too many clients already). However, if a connection pooler or firewall acts on behalf, you might see Connection refused because the pooler's health check fails. Check current usage:

psql -U postgres -c "SELECT count(*) FROM pg_stat_activity;"
psql -U postgres -c "SHOW max_connections;"

If near the limit, either increase max_connections (requires restart and careful resource consideration) or offload connection management with a pooler like PgBouncer.

7. TCP Wrappers and pg_hba.conf (Less Common)

TCP wrappers (/etc/hosts.allow, /etc/hosts.deny) are rarely used now but can cause refusals. Check:

grep postgres /etc/hosts.deny /etc/hosts.allow

pg_hba.conf controls authentication, not initial TCP acceptance. A misconfigured pg_hba.conf leads to FATAL: no pg_hba.conf entry for host after the TCP handshake, so it's not a cause of Connection refused. But always verify it permits the client's IP with the correct authentication method.

Practical Fixes and Preventive Best Practices

Once the root cause is identified, apply the minimal fix. Below are common scenarios and their targeted solutions.

Fix 1: PostgreSQL Service Down

sudo systemctl start postgresql
# Ensure it starts on boot
sudo systemctl enable postgresql

Fix 2: Listen on All Interfaces

# Edit postgresql.conf
listen_addresses = '*'
# Reload
sudo systemctl reload postgresql

Fix 3: Firewall Opening

# Example UFW
sudo ufw allow 5432/tcp comment 'PostgreSQL production access'

Fix 4: SELinux Policy Correction

sudo setsebool -P postgresql_can_network_connect on
sudo semanage port -a -t postgresql_port_t -p tcp 5432

Fix 5: Increase max_connections (with care)

# In postgresql.conf
max_connections = 300   # from default 100, adjust based on RAM
# Restart required
sudo systemctl restart postgresql

Always pair with connection pooling to avoid over-committing memory.

Best Practices to Avoid 'Connection refused' in Production

Conclusion

A Connection refused error in production is almost always a sign of a missing listener: either PostgreSQL is not running, it's listening only locally, a firewall is blocking the port, or security modules like SELinux are interfering. By walking through a disciplined root cause analysisโ€”checking process state, listen configuration, port, firewall, and mandatory access controlsโ€”you can pinpoint the culprit in minutes instead of hours. Apply the minimal targeted fix, then invest in monitoring, configuration management, and connection pooling to prevent the error from reoccurring. A production database should never be a mystery: when it refuses connections, your diagnostic playbook should be ready.

๐Ÿš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started โ€” $23.99/mo
โ† Back to all articles