Securing PostgreSQL Port 5432: Best Practices for External Database Access in Production Environments


2 views

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.