News
🛡️ Security Tutorials Audit Open Ports with ss and nmap

Audit Open Ports with ss and nmap

Find every service listening on your machine or a remote host, understand what is exposing itself to the network, and close what should not be open.

A port audit is one of the first things to run on a new server and something worth revisiting periodically. Services get installed and left running. Default configurations listen on all interfaces when they should listen on loopback only. An audit makes the exposure visible.


ss — what is listening on this machine

ss (socket statistics) replaces the older netstat and is available on all modern Linux systems.

ss -tulnp

Flags:

  • -t — TCP sockets
  • -u — UDP sockets
  • -l — listening sockets only
  • -n — show port numbers instead of service names
  • -p — show the process using each socket (requires root for other users' processes)

Output:

Netid  State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
tcp    LISTEN  0       128     0.0.0.0:22           0.0.0.0:*          users:(("sshd",pid=823,fd=3))
tcp    LISTEN  0       128     127.0.0.1:5432       0.0.0.0:*          users:(("postgres",pid=1102,fd=5))
tcp    LISTEN  0       4096    0.0.0.0:80           0.0.0.0:*          users:(("nginx",pid=991,fd=6))
tcp    LISTEN  0       4096    0.0.0.0:443          0.0.0.0:*          users:(("nginx",pid=991,fd=7))

The Local Address column is what matters:

  • 0.0.0.0:PORT — listening on all IPv4 interfaces, reachable from the network
  • 127.0.0.1:PORT — listening only on loopback, not reachable from outside
  • :::PORT — listening on all IPv6 interfaces

Postgres in the example above is bound to loopback — correct for a database that should not be directly accessible from outside. If it were 0.0.0.0:5432, that would be a problem.


Filter for unexpected listeners

sudo ss -tulnp | grep LISTEN | grep -v "127.0.0.1"

This shows only ports listening on non-loopback addresses. Compare the result against what you expect to be public. Anything unexpected — a development server, an admin interface, a database — should be investigated.


Check a specific port

ss -tulnp | grep :3306

nmap — scan a host from the outside

ss shows what the machine thinks it is listening on. nmap shows what is actually reachable from the network — the view an attacker would have.

# Scan a specific host
nmap 192.168.1.10

# Include service and version detection
nmap -sV 192.168.1.10

# Scan all 65535 ports (slow but thorough)
nmap -p- 192.168.1.10

# Scan your own machine from the outside
nmap -sV $(curl -s ifconfig.me)

Install nmap if needed:

sudo apt install nmap    # Debian / Ubuntu
sudo dnf install nmap    # RHEL / Fedora

Output:

PORT    STATE  SERVICE  VERSION
22/tcp  open   ssh      OpenSSH 9.3 (protocol 2.0)
80/tcp  open   http     nginx 1.24.0
443/tcp open   ssl/http nginx 1.24.0

Compare this against your expected exposure. A port showing as open in nmap that should be behind a firewall means the firewall rule is missing or wrong.


Close a port

Stop the service:

sudo systemctl stop service-name
sudo systemctl disable service-name

Stopping the service removes the listener immediately. Disabling it prevents it from starting again on reboot.

Restrict the bind address:

If the service needs to run but should only be accessible locally, configure it to listen on loopback. For most services, this is a config file setting:

# PostgreSQL: postgresql.conf
listen_addresses = 'localhost'

# Redis: redis.conf
bind 127.0.0.1

# MySQL: my.cnf
bind-address = 127.0.0.1

Block with a firewall:

If you cannot change the bind address, block the port at the firewall:

sudo ufw deny 3306/tcp

Use the firewall as a last resort — it is better to not listen publicly in the first place than to rely on a firewall rule not to be bypassed.


Run a regular audit

Port exposure changes over time as packages are installed, services are started, and configurations drift. Running ss -tulnp monthly — or after any significant infrastructure change — catches exposure before it becomes a problem.