
five postgres settings that stop easy breaches
the hook
most postgres breaches you read about online aren't some genius zero-day exploit. it's a database that was left wide open on the internet with default settings, waiting for a script to find it. shodan indexes these things by the thousands. the good news is that locking this down takes maybe ten minutes and five settings. let's go through them.
stop listening to the whole internet
the first thing to check is listen_addresses in your postgresql.conf. if it's set to 0.0.0.0, your database is accepting connection attempts from any ip on earth. that's the setting that turns "internal database" into "public target."
listen_addresses = 'localhost'
if your app and database live on the same box, localhost is all you need. if you've got a separate app server, bind to the specific private ip that server uses, not the whole world. the goal is simple, nobody outside your trusted network should even be able to say hello to this port.
encrypt the connection, not just the data
ssl being off means credentials and query data travel in plain text between your app and your database. anyone sniffing traffic on that network segment, whether that's a compromised router, a shared cloud environment, or a misconfigured proxy, gets to read everything.
ssl = on
this alone doesn't do much without certs configured too, so pair it with proper ssl_cert_file and ssl_key_file entries. think of listen_addresses as the locked door and ssl as making sure nobody can read what's being said through the window.
fix the auth method before someone else does
older postgres installs default to md5 authentication in pg_hba.conf. md5 hashing for passwords is weak by modern standards and vulnerable to offline cracking if the hash ever leaks. scram-sha-256 is the current standard and it actually resists that kind of attack.
host all all 0.0.0.0/0 scram-sha-256
check your pg_hba.conf file for any line still using md5 or, worse, "trust," which means no password at all. trust auth is fine for a throwaway local dev box and a disaster everywhere else. swap those lines to scram-sha-256 and reload.
kill the superuser habit
a lot of setups just run everything as the postgres superuser because it's easy. easy for you, easy for an attacker too. if that account gets compromised through a leaked connection string or an sql injection bug in your app, superuser means full control of the entire cluster, not just one database.
CREATE ROLE app_user WITH LOGIN PASSWORD 'strong-password-here';
GRANT CONNECT ON DATABASE mydb TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
give each application its own role with only the permissions it actually needs to function. no createdb, no createrole, no superuser. this way if that credential leaks, the blast radius is a lot smaller than "the entire database is gone."
turn on the paper trail
log_connections tells postgres to record every connection attempt, successful or not, in the log files. without this, you have zero visibility into who's poking at your database and when. it's the difference between noticing a break-in attempt and finding out three weeks later from a breach notification.
log_connections = on
log_disconnections = on
pair this with actually looking at your logs occasionally, or better, shipping them somewhere that alerts you on repeated failed auth attempts. logging that nobody reads isn't a defense, it's just disk usage.
the takeaway
none of these five settings are exotic. they're basic hygiene that a shocking number of production databases skip because the defaults "just worked" during setup. go check your postgresql.conf and pg_hba.conf right now, make these five changes, restart the service, and you've just removed yourself from the pile of low-hanging fruit that automated scanners feast on every day. that's the whole game with defense, you don't need to be unhackable, you just need to not be the easy one.