Every service on your server that listens on a port is a door. Some should be open — SSH, your website. Others, like a database or Redis, should never face the internet, and exposed ones are among the most common ways servers get broken into. Here is how to see which ports are open, from the inside and from the outside, and how to close the ones that shouldn't be.
From the inside: what is listening
sudo ss -tulpn
ss lists every listening socket: -t TCP, -u UDP, -l listening, -p the process, -n numbers instead of names. The column to read is Local Address:Port:
0.0.0.0:5432or[::]:5432or*:5432— listening on every network interface, including the public one. Reachable from the internet unless a firewall stops it.127.0.0.1:5432or[::1]:5432— this server only. Nothing outside can connect.10.0.0.5:5432— a private network address: reachable from other machines on that network only.
If ss isn't available, sudo lsof -i -P -n | grep LISTEN shows the same thing.
From the outside: what the internet can reach
Listening on all interfaces doesn't mean reachable — a firewall may block it. The only way to be sure is to test from another machine, the way an attacker would. From your laptop or a second server (scan only servers you own):
nmap -Pn 198.51.100.20 # the 1,000 most common ports nmap -Pn -p- 198.51.100.20 # all 65,535 ports (slower)
-Pn skips the ping check, since many servers don't answer pings. To test a single port without nmap:
nc -zv 198.51.100.20 6379
"succeeded" or "open" means the internet can reach it. A timeout means something is blocking it.
Ports that should almost never be public
- 3306 MySQL, 5432 PostgreSQL, 27017 MongoDB, 9200 Elasticsearch — databases are scanned for constantly, and exposed ones get their data stolen or ransomed.
- 6379 Redis and 11211 Memcached — often have no password at all.
- 2375 Docker API — an open one gives anyone root on the server.
- 3000, 5000, 8000, 8080 — usually development servers or admin panels that were meant to sit behind a proxy.
How to close a port
Pick the first one that fits — they go from best to last resort.
1. Stop the service if you don't need it
sudo systemctl disable --now redis-server
2. Make it listen on this server only
Most services have one setting for this. Change it, then restart the service:
- MySQL / MariaDB:
bind-address = 127.0.0.1in/etc/mysql/mysql.conf.d/mysqld.cnf - PostgreSQL:
listen_addresses = 'localhost'inpostgresql.conf - Redis:
bind 127.0.0.1 -::1in/etc/redis/redis.conf - MongoDB:
bindIp: 127.0.0.1undernet:in/etc/mongod.conf
3. Block it in the firewall
sudo ufw deny 6379/tcp
Or allow it only from the one server that needs it: sudo ufw allow from 10.0.0.7 to any port 5432 proto tcp. More on firewall rules in how to block an IP address on Linux.
The Docker trap
A container started with -p 5432:5432 is published on every interface, and Docker writes its own firewall rules that skip ufw entirely. So ufw can say a port is denied while the internet reaches it anyway. Publish to localhost instead:
docker run -p 127.0.0.1:5432:5432 postgres # or in docker-compose.yml: # ports: # - "127.0.0.1:5432:5432"
This is exactly the kind of exposure that only a scan from the outside catches.
Check again after every change
Ports open up quietly: a package update, a new container, a teammate testing something. Run ss -tulpn and an outside scan whenever you change a server. ThreatGuard does this for you — it compares what each agent sees listening with what an outside scan can actually reach every 6 hours, and flags risky ports with what to do about each one.