Auditing Open Ports and Shutting Down Risky Services
Use ss and nmap to find out what your server is actually exposing, pull the wrong things back inside, and hold a minimal attack surface.
When a server gets compromised, the culprit is usually some service you forgot was even running, sitting wide open to the internet. A database, a cache, a debug panel, a leftover app from last month — every extra open port is one more door for scanners and brute-force bots. Auditing your ports and killing what you don't use is one of the highest-payoff security habits you can build.
Step 1: See what your machine is listening on
Log into the server and start with what it's exposing locally. Reach for ss, the modern replacement for netstat:
# -t TCP -u UDP -n numeric ports -l listening -p show process
sudo ss -tunlp
Focus on the Local Address:Port column:
- 127.0.0.1:5432 — bound to loopback only, unreachable from outside. Safe.
- 0.0.0.0:5432 or :5432 — bound to every interface, reachable from the internet. Dangerous.
- [::]:3306 — the IPv6 "all addresses," exposed just the same.
0.0.0.0 and :: are the flags to watch for. On older systems sudo netstat -tunlp gives the same information.
Step 2: Scan it from the outside
The local view misses the effect of your firewall and cloud security group. What you really need to confirm is what the public internet can actually reach. From a different machine (or your laptop), point nmap at the server's public IP:
# Scan the common top 1000 ports
nmap <your-server-ip>
# All ports + service/version detection (slower, more thorough)
nmap -p- -sV <your-server-ip>
An external scan shows the surface an attacker sees. If 3306 (MySQL), 5432 (PostgreSQL), 6379 (Redis), 27017 (MongoDB), or 9200 (Elasticsearch) turns up here, it's almost certainly a misconfiguration.
Step 3: Spot what shouldn't be open
Walk through the usual high-risk offenders:
- Databases facing the public internet: an exposed MySQL, PostgreSQL, or MongoDB is trivially scanned and brute-forced.
- Redis/Memcached with no password: port 6379 open to the world is essentially handing over the server — a top entry point for cryptominers.
- Old, forgotten services: test environments, a previous app version, a debug port opened "just for a minute."
- Admin panels: database managers and monitoring dashboards should never be directly public.
The test is simple: does this port need to be reachable from the internet? If not, pull it back in.
Step 4: Lock it down — bind local, stop, firewall
Work through these in order, tightening at each layer.
1. Bind to loopback only. If a service is only used by the machine itself, edit its config to bind 127.0.0.1. For Redis in redis.conf:
bind 127.0.0.1 -::1
Set PostgreSQL to listenaddresses = 'localhost' and MySQL to bind-address = 127.0.0.1. Restart the service, then go back to Step 1 to verify.
2. Disable and remove what you don't need.
sudo systemctl disable --now <service> # stop it and prevent auto-start
sudo apt purge <package> # remove it entirely once you're sure
3. Add a firewall as a backstop. Even if a service binds 0.0.0.0, a firewall keeps outsiders out. Use ufw with a default-deny, allowlist approach:
sudo ufw default deny incoming
sudo ufw allow 22/tcp # SSH — don't lock yourself out
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
If your provider offers a cloud security group, configure it just as tightly so it and the host firewall reinforce each other.
Summary
Security comes down to a minimal attack surface: every open port needs a clear reason to exist. Make it a routine — run ss -tunlp for the local view, confirm from outside with nmap, and watch for 0.0.0.0 and any database or Redis ports. Bind unneeded services back to 127.0.0.1, disable and purge what you've retired, and let a default-deny firewall catch the rest. Re-scan after every deployment so you close the doors you left ajar before anyone else finds them.