Check which ports are really open
There are two questions here, and people often answer only one. What is listening on the server? And what can the internet actually reach? You need both answers, and the second one has to come from outside.
Published . Any current Linux server with iproute2 (ss), such as Ubuntu or Debian; nmap runs on the machine you scan from.
- Listening is not the same as reachable
- See what is listening
- Read the local address column
- Why checking from the server misleads
- Scan from outside with nmap
- Read the results
- Close what shouldn't be open
Listening is not the same as reachable
A service can listen on a port that the firewall blocks. That is fine. A service can also be reachable even though your firewall rules say the port is closed, for example when Docker publishes it. That is not fine.
So you check twice. On the server, you list what listens and on which address. From another machine, you scan to see what actually answers. Then you compare the two lists.
See what is listening
sudo ss -tulpn
The flags, from the ss manual:
-tand-u: TCP and UDP sockets.-l: only listening sockets.-p: show the process that owns each socket.-n: show port numbers, not service names.5432is clearer thanpostgresql.
Run it with sudo so you see the process for every socket, including ones owned by other users. Add -4 or -6 to show only IPv4 or IPv6 sockets.
Read the local address column
The column that matters is the local address. It tells you who can connect:
0.0.0.0:5432: every IPv4 address on the server, including the public one.[::]:5432: every IPv6 address, including a public one if the server has it.*:80: every address.127.0.0.1:6379or[::1]:6379: loopback only. Only programs on the server itself can connect.198.51.100.20:8080: only that one address.
Anything on loopback is out of reach from the internet whatever your firewall does. Anything on 0.0.0.0, [::] or * is only as safe as the firewall in front of it.
For each line on all addresses, ask: does this need to be public? SSH, a web server on 80 and 443, maybe a mail server. A database, a cache or an admin panel almost never does. Those should listen on 127.0.0.1, or sit behind a firewall rule that only lets specific addresses in.
Why checking from the server misleads
It's tempting to test with nmap localhost or curl on the server. That doesn't tell you what the internet sees:
- A connection from the server to itself goes over the loopback interface. Firewalls normally let loopback traffic through, so every listening port looks open.
- Traffic to a Docker-published port takes a different path through the firewall than traffic to the host. UFW rules can look right and still not apply. See Docker bypasses UFW.
- Your provider may run its own firewall in front of the server, often called a cloud firewall or security group. You can't see it from inside, and it can block or allow ports your server's rules don't mention.
The only check that counts is one that comes from outside, over the same path an attacker would use.
Scan from outside with nmap
Run nmap on another machine: your laptop on a home connection, or a second server. Only scan servers you own or have permission to test. Scanning other people's machines can break your provider's terms or the law.
# the most common 1,000 TCP ports
nmap -Pn your.server.example
# all TCP ports, 1 to 65535
nmap -Pn -p- your.server.example
-Pn skips host discovery and scans anyway. Without it, nmap may decide a server that ignores pings is down and skip it. -p- scans every port from 1 to 65535. It takes longer, but services on unusual ports are exactly the ones people forget.
UDP needs a separate scan, and root, because unprivileged users can only run connect scans. UDP is slow, so pick the ports you care about:
sudo nmap -Pn -sU -p 53,123,161 your.server.example
Read the results
nmap reports a state for each port:
open: something accepts connections there. This is what the internet can reach.closed: the port is reachable, but nothing listens on it.filtered: packet filtering stops the probes, so nmap can't tell. This is what a dropping firewall looks like.open|filtered: nmap can't tell which of the two. You'll see this often with UDP.
Now compare with the ss list:
- Listening on all addresses and
openfrom outside: exposed. Decide if it should be. - Listening on all addresses and
filteredfrom outside: the firewall is doing its job. openfrom outside but not in your UFW rules: almost always Docker or a provider firewall rule.
Close what shouldn't be open
The best fix is to stop the service from listening publicly. Bind it to 127.0.0.1 in its own config, restart it, and run ss again. If it must stay on all addresses, block or narrow it in the firewall, as in setting up UFW safely. For the two most common cases, see PostgreSQL exposed to the internet and Redis exposed.
Then scan from outside once more. A fix you haven't checked from outside is only a guess.
Stenvakt checks this for every server. Its Review lists what listens on all addresses, and the database guard flags databases that are exposed to the internet or have risky settings, with Back up now before you change anything. It's a Mac app and isn't out yet: get early access.
Sources
- ss(8) manual page (man7.org)
- Nmap Reference Guide: Port Scanning Basics
- Nmap Reference Guide: Port Specification and Scan Order
- Nmap Reference Guide: Port Scanning Techniques
- Nmap Reference Guide: Host Discovery