Alerts This Week
Warning Icon 1 615
Alerts This Week
Warning Icon 1 615

Guide to Auditing UFW Firewall Rules on Long-Term Linux Environments

31.Lock DigitalRoom Esm H446

Over time, it’s common for the same service to be allowed by more than one rule. An older broad rule may still match traffic first, while newer, more restrictive rules below it are never evaluated.

If you already know UFW, the challenge is not writing rules. It’s auditing existing UFW rules to identify which still define exposure, which no longer serve a purpose, and which don’t affect traffic due to rule order, IPv6 parity, or underlying iptables behavior.

This guide focuses on auditing. You’ll use ufw status numbered to inventory active rules, ufw show raw to confirm enforcement order, and validation steps to reconcile what UFW reports with what the host is actually listening on.

Why UFW Rules Stop Matching Real Exposure on Long-Lived Servers

Most searches that lead here start with a mismatch. ufw status says a port is blocked, but it’s still reachable. The rules look strict, yet the host is exposed in ways that aren’t obvious from the summary output. That’s the condition you’re auditing.IT Administrator Checking Server Logs On A Monitor Esm W400

On long-lived servers, it’s often unclear which rules are actually controlling access. The same service often ends up allowed in more than one way. Rules remain after a service changes ports or is no longer running. Temporary access added for debugging or migrations never gets cleaned up. A policy can look locked down in ufw status, while an early Anywhere rule still defines access. IPv4 gets reviewed and tightened while IPv6 remains permissive.

Another common issue is incomplete visibility. Not all logic comes from the UFW CLI. Older hosts often include custom rules in /etc/ufw/before.rules, such as NAT handling or ICMP filtering, that run before user-defined UFW rules. If you don’t check that file, you’re not seeing the full enforcement path.

A UFW ruleset is ready for restrictive changes only when you can answer two questions without digging or guessing. What the host is currently exposed to, and which specific rule allows a given connection. If you can’t point to a line number and explain why it matches, tightening rules is premature.

For long-lived hosts, auditing needs to happen before any restrictive changes. Until you identify which rules actually allow inbound traffic, changing the policy usually adds complexity instead of reducing it.

How to Audit UFW Rules Before You Harden Anything

Starting with restrictive changes before understanding exposure usually creates more rules, not less clarity. Without knowing which rules actually define access, tightening tends to pile exceptions on top of exceptions, making the ruleset harder to reason about, not safer.

The audit in this guide follows the same order UFW uses to evaluate traffic. Each step answers a specific question about how your UFW rules behave on the host, and each one depends on the previous step.

  1. Pull the active policy view.
    Start with what is enforced right now. This is the complete set of UFW rules that apply to incoming traffic, not what you remember adding, nor what the documentation says should exist.
  2. Identify the rules that actually allow traffic.
    Broad allow rules usually define real exposure, even when more restrictive rules exist for the same service. Note: at the end of this step, you should be able to name the rule that actually allows traffic for each exposed service and explain why it wins. 
  3. Verify rule order and enforcement.
    UFW applies first-match logic under the hood. If a broad rule matches early, narrower rules below it may never apply. This step confirms which rule actually decides a connection.
  4. Confirm IPv4 and IPv6 parity.
    Exposure often differs between stacks. A service restricted to IPv4 may still be reachable over IPv6. Both need to reflect the same intent.
  5. Validate with logging when behavior doesn’t match expectation.
    When rules look correct, but traffic behaves differently, logging is how you confirm what is really matching. This is about observation, not long-term monitoring.

The order matters. Audit first, prove enforcement, then harden. If you reverse it, you usually don’t reduce exposure. You add new rules while the old ones still match, and the ruleset gets harder to explain. 

Step 1: Audit Active UFW Rules With ufw status numbered

This step answers one question only. What is this host exposed to right now, based on the declared UFW rules? You are not proving enforcement yet. You are inventorying what the ruleset claims is allowed.

Start by confirming UFW is active:

sudo ufw status

Then pull the numbered ruleset. The line numbers matter later when you start removing or consolidating rules.

sudo ufw status numbered

During audits, it’s also common to add context so you can see defaults, logging state, and policy direction:

sudo ufw status verbose

At this stage, resist the urge to fix anything. You’re reading for shape and volume, not correctness.

First Pass: Scope and Volume

Don’t overthink this. Run ufw status numbered and just get a feel for the mess.

  • How many rules are we dealing with? 12 is one thing. 80 is a different problem.
  • How many are Anywhere? Those are usually the ones that matter.
  • How many ports show up more than once? That’s almost always drift: old rules that never got cleaned up.

If the ruleset is huge and half of it is Anywhere, assume this host has had “temporary” access added over the years, and nobody ever came back to remove it.

Second Pass: Group Rules by Service

Don’t read this top to bottom like it’s a story. Treat it like you’re triaging.

Group it in your head by what it’s protecting:

  • SSH: everything that touches port 22 (or whatever it got moved to)
  • Web: 80/443, plus whatever random app ports are pretending they’re web
  • Monitoring: node exporter, agents, metrics ports, etc.
  • Databases: MySQL/Postgres, Redis, anything that should never be public
  • “Why is this here?”: the weird ports you don’t recognize

This is where stale access shows up fast. If SSH is ALLOW Anywhere, that’s your real exposure. Treat it as public SSH and move on.

Third Pass: Identify Dominant Allowances

Broad allows usually define exposure. A single Anywhere rule often matters more than several narrow ones below it. Duplicate rules usually point to an old intent that was never removed. These are the rules you’ll validate and harden later.

Example: Reading a Realistic Ruleset

[ 1] 22/tcp           ALLOW IN    Anywhere
[ 2] 22/tcp           ALLOW IN    203.0.113.10
[ 3] 80/tcp           ALLOW IN    Anywhere
[ 4] 443/tcp          ALLOW IN    Anywhere
[ 5] 9100/tcp         ALLOW IN    10.0.0.0/8
[ 6] 9100/tcp         ALLOW IN    Anywhere
[ 7] 3306/tcp         ALLOW IN    10.0.5.0/24
[ 8] 22/tcp (v6)      ALLOW IN    Anywhere (v6)

In this ruleset, SSH traffic hits line 1 first. That rule allows SSH from anywhere, so the connection is accepted immediately. Line 2 never gets a chance to apply, even though it looks more restrictive.

The same thing happens with the monitoring port. Line 6 allows port 9100 from Anywhere. Because it appears above, nothing more restrictive for that port defines access. Line 5 does not reduce exposure.

Line 8 shows the same pattern on IPv6. SSH is allowed from Anywhere over IPv6, even though IPv4 access looks more controlled.

That’s why multiple rules can exist for the same service, but only the first matching one affects access. The first rule that allows the traffic is the one that defines what the host is exposed to.

Identify Stale Rules Using Temporary UFW Logging

Sometimes you can’t tell whether a rule is still needed just by reading it. Use temporary UFW logging during a short audit window, then turn it back down.

Increase logging temporarily during an audit window:

sudo ufw logging high

Verify the logging state:

sudo ufw status verbose

Watch UFW logs in real time:

sudo journalctl -f | grep -i ufw

If your environment logs to a file instead:

sudo tail -f /var/log/ufw.log

You’re not doing deep log analysis here. You’re confirming whether traffic still matches a rule you’re considering removing. Once you’ve answered that, logging should go back to its previous level before you move on.

Step 2: Confirm Rule Order and Enforcement With ufw show raw

Step 1 shows what the ruleset claims is allowed. That’s not the same as what actually decides a connection. This step answers a narrower question. Which rule wins when traffic hits the host?

UFW doesn’t enforce packets by itself. It translates your policy into iptables rules, and that’s what actually gets evaluated. Order matters there, and the first match decides the outcome. If you don’t look at the generated rules, it’s easy to trust a summary that doesn’t reflect enforcement.

Show the generated rules exactly as UFW loads them:

sudo ufw show raw

Think of UFW rules as a series of gates. If the first gate is wide open, the packet walks through immediately. Anything below it, no matter how strict, never even gets evaluated. This is why an audit has to prove which rule matches first, not just which rules exist.

This output is the enforcement path. Rules are evaluated top to bottom. A broad rule early in the chain will accept traffic before a narrower rule below it ever gets a chance to apply.

This is a common reason the summary output doesn’t reflect actual behavior. The policy looks tight in ufw status numbered. The raw output shows a permissive match earlier that defines access.

Prove it: why a narrow rule doesn’t matter

Consider a simplified example from ufw show raw:

-A ufw-user-input -p tcp --dport 22 -j ACCEPT
-A ufw-user-input -p tcp -s 203.0.113.10 --dport 22 -j ACCEPT

Both rules allow SSH. The second one is narrower. It never matters. Any SSH connection that matches the first rule is accepted immediately. The presence of the restricted rule below it does not reduce exposure.

This is why rule count and intent don’t tell you much on their own. Order does.

To separate intent from enforcement, it also helps to see which rules were added explicitly through the UFW CLI:

sudo ufw show added

This view is useful later when you clean up rules. It shows what was added deliberately, not what exists after rendering and defaults are applied.

Finally, validate the firewall view against what the host is actually listening on:

sudo ufw show listening

Many admins also cross-check directly at the socket level:

sudo ss -tulpen

At this point, you should be able to reconcile three things. What services are listening, what UFW rules claim to allow them, and which raw rule actually decides the connection. If you are still unsure, check out our UFW Troubleshooting blog

Docker Reality Check: When UFW Rules Don’t Match Reachable Ports

On long-lived hosts, Docker is one of the most common reasons UFW policy and real exposure diverge. Docker programs iptables directly. That includes NAT and forwarding paths that sit outside the usual UFW flow.

Most admins assume UFW is deciding whether a port is reachable. With Docker, that’s often not true. When you run docker run -p 80:80, Docker inserts NAT and forwarding rules (including PREROUTING and DOCKER-related FORWARD logic) that can make a port reachable even if ufw status looks strict. If you don’t inspect the underlying chains, it’s easy to mistake declared UFW policy for actual exposure.

If Docker is present, don’t stop at UFW output. Verify the underlying rule path.

Start with the full ruleset and Docker-specific chains:

sudo iptables -S
sudo iptables -S DOCKER-USER
sudo iptables -S FORWARD
sudo iptables -t nat -S

If you prefer counters and packet visibility:

sudo iptables -L DOCKER-USER -n -v
sudo iptables -t nat -L -n -v

If ufw status numbered says a port is blocked, but Docker published it, the host may still be reachable on that port. The audit step here is confirming the actual reachable surface, not relying on UFW’s declared intent alone.

At this point, you’ve verified declared policy, enforcement order, and actual listeners. You now know what the rules say, how they’re enforced, and where UFW may not be the deciding layer. Hardening only makes sense after that picture is clear.

Step 3: Identify the UFW Rules that Actually Allow Inbound Traffic

After auditing the ruleset and confirming the enforcement order, the next task is deciding which UFW rules actually matter. Not every rule contributes to exposure. A small number of permissive rules usually define what the host allows.

Work through the ruleset service by service. For each exposed service, apply the same checks.

Identify the Most Permissive RuleBlurry Data Center Esm W400

Look for the rule with the widest scope. Anywhere sources, unrestricted interfaces, or broad subnets. That rule usually defines exposure, regardless of how many narrower rules exist below it.

Once you identify it, treat it as the rule that represents the service’s real exposure. If you had to describe access in one sentence, this is the rule you’d use.

Identifying Redundant or Overlapping Allows

After you find the most permissive rule, look for other rules that allow the same traffic but with narrower conditions. If a rule matches, iptables stops and applies that target. Rules below it won’t change exposure. They often reflect older access paths, temporary exceptions, or later attempts to tighten access without removing the original rule.

During an audit, the goal is not to decide what to delete. It’s to identify which rules actually affect traffic and which ones don’t change behavior at all.

Write a One-Line Exposure Summary Per Service

For each exposed port or service, you should be able to write a single line that describes access as enforced:

  • SSH: allowed from Anywhere (IPv4), Anywhere (v6)
  • MySQL: allowed only from 10.0.5.0/24
  • Node exporter: allowed from Anywhere because of rule 6

If you can’t summarize exposure without rereading the ruleset, you don’t yet know which rule is actually controlling access.

At the end of this step, you should be able to identify the dominant rule (the first rule that actually allows the traffic) for each exposed service and explain why it wins.

Final Thoughts

Don’t leave this as “the rules look fine.” The goal was to prove what’s actually happening on this host. You started with what UFW says (status), checked what it really loads (show raw), reconciled that against what’s listening (show listening / ss), and used logging when the behavior still didn’t line up. If you can’t point to the first rule that matches and explain why it wins, you’re not done yet.

If you want the bigger picture behind why UFW summaries drift from enforcement on long-lived hosts, how UFW fits into Linux firewalls covers where UFW sits in the stack and what it abstracts away.

If you’re ready to tighten access without breaking the box, UFW Hardening Patterns for Long-Lived Linux Servers picks up from here with add → test → remove, rollback safety, and validation checks so changes stay boring.

Your message here