Network exposure
Inventory listening services
Find which processes listen on which interfaces and decide whether each port should be public, private, or local-only.
A firewall rule is easier to reason about when we know what is actually listening. Start from the process and interface.
Use ss to inspect TCP and UDP listeners, then map each socket to its service and owner:
sudo ss -lntup
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=712,fd=3))
tcp LISTEN 0.0.0.0:5432 users:(("postgres",pid=1103,fd=6))
tcp LISTEN 127.0.0.1:3000 users:(("node",pid=1288,fd=19))
Read the local address column carefully. 127.0.0.1:3000 is loopback only: reachable by processes on this host, invisible from the network. 0.0.0.0:5432 listens on every interface the server has.
PostgreSQL on 0.0.0.0:5432 is reachable through every server interface even if the app uses localhost. A firewall may hide it today, but binding it narrowly removes another exposure path.
Fix the binding at the source
A database used only by the local application should bind to loopback or a private network. For PostgreSQL that is one line in /etc/postgresql/16/main/postgresql.conf:
listen_addresses = 'localhost'
Restart the service and run sudo ss -lntup again — the entry should now read 127.0.0.1:5432. Remove services the server does not need at all instead of rebinding them.
Verify from outside
The listener table is the inside view. Confirm the outside view from another machine:
nc -vz 203.0.113.10 5432
# nc: connect to 203.0.113.10 port 5432 (tcp) failed: Connection refused
Capture UDP listeners and container-published ports too. A service may not appear in the expected application configuration while Docker or a socket-activated unit still exposes it. A -p 8080:8080 in a compose file publishes the port on all interfaces, and it will not show up in any config file under /etc.
Save listener output with process, owner, address, port, and purpose. Stop or rebind one unnecessary listener, then verify locally and from an external host that only intended paths work.
Lesson completed