Opening port 5432 to the internet is equivalent to putting a bullseye on your database server. PostgreSQL's authentication is robust, but brute force attacks, zero-day exploits, and credential stuffing attempts will flood your logs. I've seen exposed instances compromised within hours through:
- Default credential attacks (postgres/postgres)
- CVE-2019-9193 (COPY TO/FROM PROGRAM exploit)
- Unpatched versions with known vulnerabilities
Start with these essential postgresql.conf tweaks:
# Minimum security settings in postgresql.conf
listen_addresses = 'specific_ip' # Never use '*'
password_encryption = scram-sha-256
ssl = on
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'
auth_delay.milliseconds = 1000 # Thwarts brute force
When VPN isn't an option, consider these network controls:
# Example iptables rules for PostgreSQL
iptables -A INPUT -p tcp --dport 5432 -s customer.ip.range -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -N PORT_SCAN
iptables -A PORT_SCAN -m recent --name ATTACK --set
iptables -A PORT_SCAN -j DROP
For JDBC connections, enforce SSL and certificate validation:
// Secure JDBC connection example
String url = "jdbc:postgresql://db.example.com:5432/mydb?"
+ "ssl=true&"
+ "sslmode=verify-full&"
+ "sslrootcert=/path/to/root.crt&"
+ "sslpassword=supersecret";
// With connection pooling (HikariCP example)
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername("limited_role_user");
config.setPassword("complex_password");
config.addDataSourceProperty("socketTimeout", "30");
Lock down access within PostgreSQL itself:
-- Example security SQL
CREATE ROLE customer_access LOGIN PASSWORD 'complex_pwd'
CONNECTION LIMIT 3
VALID UNTIL '2024-12-31';
REVOKE ALL ON DATABASE prod_db FROM PUBLIC;
GRANT CONNECT ON DATABASE prod_db TO customer_access;
-- Row-level security for sensitive tables
ALTER TABLE customer_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY customer_policy ON customer_data
FOR SELECT TO customer_access
USING (customer_id = current_setting('app.current_customer_id')::int);
Implement these logging measures in pg_hba.conf:
# Log all connection attempts
hostssl all all 0.0.0.0/0 scram-sha-256
log_connections = on
log_disconnections = on
log_hostname = on
log_statement = 'ddl'
log_duration = on
Combine this with fail2ban rules to automatically block brute force attempts:
# fail2ban filter for PostgreSQL
[postgresql]
enabled = true
port = 5432
filter = postgresql
logpath = /var/log/postgresql/postgresql-%Y-%m-%d.log
maxretry = 3
findtime = 3600
bantime = 86400
When business requirements mandate exposing PostgreSQL's default port 5432 publicly, we enter dangerous territory. While modern PostgreSQL (v14+) has improved security over legacy FTP services, an exposed database interface remains a high-value attack surface for:
- Credential stuffing attacks (over 2.7M brute force attempts/day observed on public DB ports)
- SQL injection via vulnerable client applications
- Zero-day exploits like CVE-2021-32027 (type confusion in array handling)
Here's a layered approach combining PostgreSQL native features and infrastructure controls:
1. pg_hba.conf as Your First Firewall
# TYPE DATABASE USER ADDRESS METHOD
host cust_db app_user 203.0.113.42/32 scram-sha-256
host all all 0.0.0.0/0 reject
Key points:
- Use SCRAM-SHA-256 instead of md5 (PostgreSQL 10+)
- Restrict to specific source IPs when possible
- Explicitly reject all other connections
2. Network-Level Protections
# iptables example for rate limiting
iptables -A INPUT -p tcp --dport 5432 -m connlimit --connlimit-above 5 -j REJECT
iptables -A INPUT -p tcp --dport 5432 -m recent --name ATTACK --set
iptables -A INPUT -p tcp --dport 5432 -m recent --name ATTACK --update --seconds 60 --hitcount 10 -j DROP
3. Connection Encryption That Actually Works
For JDBC connections that bypass VPN:
jdbc:postgresql://db.example.com:5432/dbname?
ssl=true&
sslmode=verify-full&
sslrootcert=/path/to/ca.pem&
sslcert=/path/to/client.crt&
sslkey=/path/to/client.key.pk8
Critical parameters:
sslmode=verify-full
prevents MITM attacks- Client certificate authentication adds 2FA
- Use TLS 1.3 (PostgreSQL 12+) via
ssl_min_protocol_version
Essential PostgreSQL logging configuration:
# postgresql.conf
log_connections = on
log_disconnections = on
log_hostname = on
log_statement = 'ddl'
log_min_duration_statement = 1000 # Log slow queries
Pair with tools like pgBadger for attack pattern analysis.
Consider these architectural alternatives:
- SSH Tunneling:
ssh -L 63333:localhost:5432 user@gateway.example.com
- PgBouncer with TLS: Front your DB with this lightweight connection pooler
- API Gateway Pattern: Expose GraphQL/REST endpoints instead of direct DB access
The most secure public PostgreSQL port is the one that doesn't exist. But when business needs demand it, these measures can reduce your attack surface by 90%+ compared to default configurations.