Rich Gibbs

The $0 firewall audit: 6 commands that catch what UFW status misses

security · firewall · linux · vps · ec2 · indie-founder · hardening · devops

Direct answer

Run six commands to audit what's actually exposed on your Linux server: ss -tulpn to see listening ports, iptables to see effective rules, ufw status to check your policy, nmap from the outside to see what attackers see, and a process trace to map each port to its owner. The gap between what you think is listening and what's actually listening is where compromise starts.

Your UFW says Status: active. Your AWS security group has three rules. You feel good. You shouldn’t — not yet.

A firewall policy is not the same thing as actual exposure. A service can bind to 0.0.0.0 before UFW even starts. A Docker container can create its own bridge network and listen on an interface UFW doesn’t manage. A background job can bind to a port you forgot you exposed six months ago.

The only way to know what’s actually reachable is to check from the ground up: what’s bound, what’s allowed, and what an attacker can see from the outside.

This is a read-only audit. Six commands. No changes. Run it on a quiet Sunday, fix what you find, then sleep better.

Command 1: ss -tulpn — what’s actually listening

This is the ground truth. Everything listening on TCP or UDP, and which process owns it.

sudo ss -tulpn

Look for these red flags:

Port What it usually is Why it matters
0.0.0.0:6379 Redis Exposed to the world, no auth by default
0.0.0.0:5432 Postgres Database reachable from anywhere
0.0.0.0:3306 MySQL Same
0.0.0.0:2375 Docker API Root shell on your server if exposed
0.0.0.0:9200 Elasticsearch Usually unauthenticated
0.0.0.0:11211 Memcached Often no auth
0.0.0.0:27017 MongoDB Historically exposed to the internet

If you see any of these bound to 0.0.0.0 or :: instead of 127.0.0.1 or a specific interface, that’s a finding. Fix the binding first, then worry about the firewall.

For anything you don’t recognize, check the process:

sudo ss -tulpn | grep ':PORT'
# or
sudo lsof -i :PORT

Then trace back: what service owns it, when did it start, and do you still need it?

Command 2: iptables -L -n -v — the actual firewall rules

UFW, firewalld, and security groups all ultimately write iptables rules. This is what the kernel enforces.

sudo iptables -L -n -v

Look at the Chain INPUT section. Every packet that reaches your server goes through these rules in order.

Common gaps:

  • A rule that allows 0.0.0.0/0 to a port that should be internal
  • A rule added by Docker that bypasses your UFW policy entirely
  • A rule you added during debugging and never removed

If you see target prot opt source destination where source is 0.0.0.0/0 and destination is a service port, verify that was intentional.

Command 3: ufw status verbose — your policy vs reality

Now check what UFW thinks it’s doing:

sudo ufw status verbose

Look for: - Status: active — good - Default: deny (incoming), allow (outgoing) — the safe default - Any ALLOW rules for ports that should be internal

Compare this to what ss -tulpn showed. If UFW says deny for port 6379 but Redis is bound to 0.0.0.0:6379, that’s a contradiction. Fix the binding, not the firewall rule.

Command 4: nmap -sT -O your-server-ip — what attackers see

Run this from a different machine, not the server itself. This is what an attacker on the internet sees.

# From your laptop or another server
nmap -sT -O your-server-ip

Or if you don’t have nmap:

# Basic TCP connect scan
for port in 22 80 443 3000 3306 5432 6379 9200 11211 2375 27017; do
  (echo > /dev/tcp/your-server-ip/$port) 2>/dev/null && echo "Port $port is open"
done

Compare this to what you expected. Every open port is an attack surface. If you see something you didn’t expect, go back to Command 1 and trace it.

Command 5: sudo lsof -i -P -n — process-to-port mapping

This shows which process owns each open port. Useful when ss doesn’t give enough detail.

sudo lsof -i -P -n

Look for: - Processes you don’t recognize - Processes running as root that shouldn’t be - Processes listening on * (all interfaces) instead of 127.0.0.1

For anything suspicious, get more detail:

sudo lsof -i :PORT
sudo cat /proc/PID/cmdline
sudo systemctl status PID

Command 6: sudo iptables -L DOCKER-USER -n -v — the Docker gap

This is the most commonly missed one. Docker creates its own iptables chains that bypass UFW entirely.

sudo iptables -L DOCKER-USER -n -v

If you see rules here that allow traffic to containers, those rules bypass your UFW policy. Docker manages its own networking and doesn’t respect ufw rules.

Fix: either use ufw-docker (a community package that makes Docker respect UFW) or explicitly bind containers to 127.0.0.1 unless they need external access.

What a clean result looks like

After running all six commands, you should have:

  1. ss -tulpn showing only the ports you expect, bound to the right interfaces
  2. iptables rules that match your intent (no 0.0.0.0/0 to internal services)
  3. ufw status matching iptables (no contradictions)
  4. nmap showing only ports 22, 80, 443 (or whatever you deliberately expose)
  5. lsof showing only recognized processes owning ports
  6. DOCKER-USER chain empty or carefully scoped

The fix pattern

For every finding, the pattern is the same:

  1. Fix the binding first. Change bind 0.0.0.0 to bind 127.0.0.1 in the service config.
  2. Then fix the firewall. Add an explicit deny rule if needed.
  3. Verify. Run nmap again from outside.

Don’t just add a firewall rule and call it done. The service will still be exposed to anything that can reach it before the rule applies.

If you want a second set of eyes

This audit takes about 30 minutes. If you want someone else to run it and hand you a prioritized report, that’s what the QuickCheck does — a read-only posture review of a single Linux box. You get a report with what I found, why it matters, and what to do next. No production changes, no credentials stored.

See what a report looks like before deciding.


Read-only audit, not a pentest. Your mileage depends on your setup. Not compliance certification.

Frequently asked questions

Why does UFW status not show everything?

UFW shows your firewall policy, not what's actually bound. A service can bind to 0.0.0.0 before UFW loads, bypassing it entirely, or bind to an interface UFW doesn't manage (like a Docker bridge or a pod network). Only 'ss -tulpn' shows the ground truth.

What is the most common forgotten port on indie VPSes?

Redis (6379) is the most frequent offender — developers bind it to 0.0.0.0 for convenience and forget to lock it down. Postgres (5432), MySQL (3306), and Docker's API (2375) are close behind.

How often should I run this audit?

Monthly, or after any new service install. If you deploy Docker containers or side projects, run it immediately after each deploy — containers are the most common source of accidental exposure.