Is your PostgreSQL exposed?
PostgreSQL only listens on localhost out of the box. Then one day you set listen_addresses = '*' to connect from your laptop, or Docker publishes 5432, and the whole internet can try passwords. Here is how to check, and how to close it without breaking your app.
Published . PostgreSQL 14 and later on any Linux distro, installed from packages or run in Docker; the commands are the same across versions.
- Two settings decide it
- Check from outside first
- Listen only where you need to
- Read pg_hba.conf line by line
- Move from md5 to scram-sha-256
- Docker publishes 5432 past your firewall
- Admin from your laptop with an SSH tunnel
Two settings decide it
Whether a stranger can log in to your PostgreSQL comes down to two files. postgresql.conf decides which network addresses the server listens on. pg_hba.conf decides who may log in from where, and how they prove it.
The default for listen_addresses is localhost, so only programs on the same machine can connect over TCP. Setting it to '*' means every interface, including the public one. That alone doesn't let anyone in, but it means every bot that scans port 5432 can now reach pg_hba.conf and start guessing passwords. If a line there says trust, it doesn't even need to guess.
Check from outside first
A test from the server itself tells you nothing about the internet. From another machine:
nmap -Pn -p 5432 your.server.example
open means the port answers. filtered or closed is what you want. Then look on the server:
sudo ss -tlnp | grep 5432
sudo -u postgres psql -c "SHOW listen_addresses;" -c "SHOW config_file;" -c "SHOW hba_file;"
A listener on 0.0.0.0:5432 or [::]:5432 is on every interface. 127.0.0.1:5432 is local only. The last two lines tell you where the files you need to edit actually live.
Listen only where you need to
If everything that uses the database runs on the same server, put it back to the default in postgresql.conf:
listen_addresses = 'localhost'
If an app server on a private network needs it, list the private address instead of '*'. This setting is only read at server start, so a reload is not enough:
sudo systemctl restart postgresql
Find out who connects before you close it. If an app on another host or in a container reaches PostgreSQL over the network, narrowing listen_addresses or pg_hba.conf cuts it off the moment you restart or reload. Look at SELECT client_addr, usename, datname FROM pg_stat_activity; first, and keep a working SSH session open while you change things.
Read pg_hba.conf line by line
Each line is a rule: connection type, database, user, client address, method. PostgreSQL uses the first line that matches the connection and stops there. There is no fall-through: if that line's method fails, later lines are not tried. If no line matches, the connection is refused.
The lines to look for:
truston anyhostline. It lets anyone who can reach the server log in as any user, with no password. On ahostline with a wide address, that is an open database.0.0.0.0/0,::0/0orallin the address column. These match every IPv4 address, every IPv6 address, or any address at all.md5orpassword.passwordsends the password in clear text.md5is deprecated and will be removed in a future release, and it offers no protection once someone steals the stored hash.
A safe starting point for a single server looks like this:
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
# one app server, one database, one user
host appdb appuser 198.51.100.20/32 scram-sha-256
You can test your edits before they take effect. The pg_hba_file_rules view reads the file from disk, and any row with a non-empty error column is a line PostgreSQL would reject:
SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules;
When it's clean, reload. Changes to pg_hba.conf only need a reload, not a restart, and apply to new connections:
SELECT pg_reload_conf();
Move from md5 to scram-sha-256
SCRAM is the strongest password method PostgreSQL has. The method in pg_hba.conf isn't the whole story: each stored password is hashed one way or the other, decided by password_encryption when the password was set. Check which roles still have MD5 hashes (needs a superuser):
SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';
For each one, make sure password_encryption = 'scram-sha-256' is set and give the role a new password, then update the app's connection settings:
SET password_encryption = 'scram-sha-256';
ALTER ROLE appuser WITH PASSWORD 'CHANGE_ME';
Once no MD5 hashes are left, change md5 to scram-sha-256 in pg_hba.conf and reload. Very old client libraries don't support SCRAM, so check your app connects before you move on.
Docker publishes 5432 past your firewall
On first start, the official image adds a pg_hba.conf line for all users from all addresses using scram-sha-256 (unless you change POSTGRES_HOST_AUTH_METHOD). Never set that variable to trust: anyone who reaches the port gets in without a password, even if one is set.
The bigger problem is ports: - "5432:5432" in Compose. Docker's own firewall rules let that traffic through even when UFW says the port is closed. If only other containers need the database, remove ports:. If something on the host needs it, publish on loopback only:
ports:
- "127.0.0.1:5432:5432"
Then run docker compose up -d to recreate the container. The full story is in Docker bypasses UFW.
Admin from your laptop with an SSH tunnel
You rarely need 5432 open to reach the database yourself. Keep it on localhost and forward a local port over SSH:
ssh -N -L 5433:localhost:5432 [email protected]
While that runs, point psql or your GUI at localhost port 5433. The traffic goes through your SSH connection and the server sees it as a local login. Stop it with Ctrl+C. Afterwards, run the nmap check from outside again and confirm 5432 no longer shows as open.
Stenvakt checks this on every server. Its Review is a read-only check of each server, and the database guard finds databases that answer from outside and risky settings like trust lines, with Back up now before you touch anything. You see a Preview of every fix before it runs, and fixes run in safe mode: a snapshot first, a rollback timer and a fresh-connection test. It's a Mac app and isn't out yet: get early access.
Sources
- PostgreSQL docs: Connections and Authentication (listen_addresses, port)
- PostgreSQL docs: The pg_hba.conf File
- PostgreSQL docs: Password Authentication (scram-sha-256, md5)
- Docker Hub: postgres official image (POSTGRES_HOST_AUTH_METHOD)
- OpenBSD manual: ssh(1) (-L and -N)