Fixing PostgreSQL 'Connection Refused' on Port 5433 After Changing the Default Port
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: PostgreSQL was told to listen on port 5433 in
postgresql.conf, but one or more of the following still points to the old port: the client connection string,pg_hba.conf, the OS firewall, or the service socket — so the TCP handshake never completes. - How to fix it: Confirm the server is actually bound to 5433 with
ss -tlnp | grep 5433, then cascade the port change through every layer:postgresql.conf→ firewall rules →pg_hba.conf→ client DSN → service restart. - Shortcut: Use our Client-Side Sandbox above to paste your
postgresql.confand connection string — it will auto-refactor both and generate the correctedpsqlcommand without sending your credentials anywhere.
The Incident — What Does This Error Mean?
psql: error: could not connect to server: Connection refused
Is the server running on host "127.0.0.1" and accepting
TCP/IP connections on port 5433?
Immediate consequence: The PostgreSQL backend process is either (a) not listening on 5433 at all — meaning the config change was never applied — or (b) listening on 5433 but a firewall (ufw, iptables, Security Group, or nftables) is silently dropping the SYN packet before it reaches the process. Either way, every application thread that holds a connection pool slot is now blocking, and your connection pool will exhaust within seconds under any real load.
The Attack Vector / Blast Radius
This is a misconfiguration-induced outage, but it carries a secondary security risk people miss:
Stale firewall rules. If you opened port 5433 without closing 5432, you now have both ports potentially exposed. If the old PostgreSQL socket is still bound (e.g., a second cluster, a zombie process, or a misconfigured
pg_ctlcluster), an attacker on your network can still reach the unpatched instance on 5432 while you're busy debugging 5433.Application fallback to default. Some ORMs and connection pool libraries (PgBouncer, SQLAlchemy, JDBC) silently fall back to port 5432 when 5433 is refused. This means your app may appear to "work" while actually hitting a different, possibly unpatched PostgreSQL instance — a ghost database with stale schema or no auth hardening.
Cascading pool exhaustion. Every retry attempt from your app server holds a thread. Under a connection-refused storm, a pool of 100 threads can exhaust in under 3 seconds, taking down the entire application tier, not just the DB layer.
How to Fix It
Step 1 — Verify What Port PostgreSQL Is Actually Bound To
# Is Postgres even running on 5433?
ss -tlnp | grep postgres
# Expected output if config was applied correctly:
# LISTEN 0 128 0.0.0.0:5433 0.0.0.0:* users:(("postgres",pid=1234,fd=5))
# If you see 5432 here, the config change was NOT applied.
Step 2 — Fix postgresql.conf (Basic Fix)
# /etc/postgresql/15/main/postgresql.conf
- port = 5432
+ port = 5433
Then restart the service — a reload is NOT sufficient for a port change:
sudo systemctl restart postgresql
# Verify:
sudo systemctl status postgresql
ss -tlnp | grep 5433
Step 3 — Update the Firewall
# UFW (Ubuntu/Debian)
- sudo ufw allow 5432/tcp
+ sudo ufw allow 5433/tcp
+ sudo ufw deny 5432/tcp # Close the old port. Do not leave it open.
+ sudo ufw reload
# iptables (RHEL/CentOS/raw)
- iptables -A INPUT -p tcp --dport 5432 -j ACCEPT
+ iptables -A INPUT -p tcp --dport 5433 -j ACCEPT
+ iptables -D INPUT -p tcp --dport 5432 -j ACCEPT
AWS / GCP / Azure: Update your Security Group inbound rule or VPC Firewall Rule. Remove the 5432 rule. Add 5433 scoped to your application subnet CIDR — never 0.0.0.0/0.
Step 4 — Update pg_hba.conf (Enterprise Best Practice)
pg_hba.conf itself doesn't contain port numbers, but if you're using peer authentication or Unix domain sockets, the socket path changes. Verify:
# /etc/postgresql/15/main/pg_hba.conf
# No port change needed here, but confirm the socket dir matches:
# postgresql.conf:
- unix_socket_directories = '/var/run/postgresql'
+ unix_socket_directories = '/var/run/postgresql' # Unchanged — correct.
# The .s.PGSQL.5433 socket file must exist after restart:
# ls /var/run/postgresql/.s.PGSQL.5433
Step 5 — Fix the Client Connection String
# Application .env / secrets manager / DSN
- DATABASE_URL=postgresql://appuser:[email protected]:5432/mydb
+ DATABASE_URL=postgresql://appuser:[email protected]:5433/mydb
# psql CLI
- psql -h 127.0.0.1 -p 5432 -U appuser mydb
+ psql -h 127.0.0.1 -p 5433 -U appuser mydb
# PgBouncer pgbouncer.ini
- host=127.0.0.1 port=5432
+ host=127.0.0.1 port=5433
Step 6 — Full Verification Sequence
# 1. Confirm port binding
ss -tlnp | grep 5433
# 2. Test TCP reachability (before even involving psql)
nc -zv 127.0.0.1 5433
# Expected: Connection to 127.0.0.1 5433 port [tcp/*] succeeded!
# 3. Test auth
psql -h 127.0.0.1 -p 5433 -U appuser -d mydb -c 'SELECT version();'
# 4. Confirm OLD port is dead
nc -zv 127.0.0.1 5432
# Expected: nc: connect to 127.0.0.1 port 5432 (tcp) failed: Connection refused
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Checkov — Scan IaC for Hardcoded Default Ports
# .checkov.yaml
checks:
- CKV_POSTGRES_PORT_NONDEFAULT # Custom check: flag if port == 5432 in prod
2. Terraform — Enforce Port Consistency Across All Resources
# variables.tf
- variable "db_port" { default = 5432 }
+ variable "db_port" {
+ default = 5433
+ description = "PostgreSQL non-default port. Must match postgresql.conf and SG rules."
+ validation {
+ condition = var.db_port != 5432
+ error_message = "Do not use the default PostgreSQL port 5432 in production."
+ }
+ }
# aws_security_group.tf — reference the variable, never hardcode
- from_port = 5432
- to_port = 5432
+ from_port = var.db_port
+ to_port = var.db_port
3. OPA/Conftest Policy — Block Mismatched Port Configs
# policy/postgres_port.rego
package postgres
deny[msg] {
input.postgresql_conf.port != input.app_config.db_port
msg := sprintf(
"Port mismatch: postgresql.conf says %v but app DSN uses %v. Deployment blocked.",
[input.postgresql_conf.port, input.app_config.db_port]
)
}
4. GitHub Actions — Pre-Deploy Port Smoke Test
# .github/workflows/db-healthcheck.yml
- name: Verify PostgreSQL port binding
run: |
nc -zv ${{ secrets.DB_HOST }} ${{ secrets.DB_PORT }} || \
(echo "FATAL: PostgreSQL not reachable on ${{ secrets.DB_PORT }}" && exit 1)
Bottom line: The port change is a 1-line edit in postgresql.conf. The outage happens because that single change has five downstream dependencies (firewall, client DSN, PgBouncer, pg_hba socket, and the service restart) that must all be updated atomically. Automate the validation — don't rely on humans to remember all five.