ufw vs iptables: What Happens When Their Rules Conflict

ufw vs iptables: What Happens When Their Rules Conflict

I ran into this on a test box a while back. I had opened port 80 with ufw allow 80. Later I added iptables -A INPUT -p tcp --dport 80 -j DROP because a guide told me to. Port 80 stayed open. So I tried again, this time inserting the rule at the top of the chain. Now port 80 was blocked, but ufw status still said it was allowed.

Neither tool was wrong. Each one only knows about its own rules, and what decides the result is something neither shows you by default: the order of the rules.

ufw is not a separate firewall

This is what confuses most people. ufw does not filter packets itself. It is a frontend that writes iptables rules for you (or nftables rules through the iptables-nft layer on newer distros). When you run this:

$ sudo ufw allow 80

ufw turns it into an iptables rule and puts it in its own chains, like ufw-user-input, ufw-before-input and ufw-after-input. The main INPUT chain jumps into those. You can check it yourself:

$ sudo iptables -L INPUT -n --line-numbers
Chain INPUT (policy DROP)
num  target     prot opt source        destination
1    ufw-before-logging-input  all  --  0.0.0.0/0  0.0.0.0/0
2    ufw-before-input          all  --  0.0.0.0/0  0.0.0.0/0
3    ufw-after-input           all  --  0.0.0.0/0  0.0.0.0/0
4    ufw-after-logging-input   all  --  0.0.0.0/0  0.0.0.0/0
5    ufw-reject-input          all  --  0.0.0.0/0  0.0.0.0/0
6    ufw-track-input           all  --  0.0.0.0/0  0.0.0.0/0

Plain iptables is the same thing without the helper on top. You edit the chains directly and decide where each rule goes. Both tools write to the same place, and that is where the conflict comes from.

What happens when you mix them

Netfilter reads a chain from top to bottom and stops at the first rule that matches. So the result depends on where each rule sits in the chain. It does not matter which tool created the rule, or which one you ran last.

Case 1: you append the iptables rule

$ sudo iptables -A INPUT -p tcp --dport 80 -j DROP

-A adds the rule at the end of INPUT, after the ufw chains. A packet for port 80 hits ufw’s ACCEPT first and is accepted right there. Your DROP rule is never reached. Port 80 stays open and your deny rule does nothing.

Case 2: you insert the iptables rule at the top

$ sudo iptables -I INPUT 1 -p tcp --dport 80 -j DROP

-I INPUT 1 puts the rule in position 1, before the ufw chains. Now the packet is dropped first and ufw’s allow rule never runs. Port 80 is really blocked, but ufw status still shows it as allowed, because ufw does not know about your rule. This is the case behind most of the “my firewall rule is not working” questions.

In short, the earlier rule wins, and ufw cannot see rules it did not create.

Does this cause real problems? Yes, Docker is the usual one

This happens a lot in real setups, and Docker is the most common reason.

Docker edits iptables on its own to publish ports (-p 8080:80 and so on). It adds its own chains and NAT rules ahead of ufw’s, and it does this no matter what ufw says. So you can run:

$ sudo ufw deny 8080

and ufw status will show the deny rule, yet the port is still reachable from outside. The traffic is redirected to the container before ufw’s rules are checked. A lot of people find this out the hard way on a public server they thought was protected.

The usual fix is the DOCKER-USER chain. Docker creates it for you so you can filter traffic before Docker’s own rules apply:

# Example: only allow one network to reach published container ports
$ sudo iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROP

fail2ban can cause a milder version of this. It adds its ban rules straight into iptables, outside ufw, so the two can disagree if you do not check where each rule landed.

How to check what is really happening

Once anything other than ufw touches iptables, do not rely on ufw status alone. Look at the real chain:

# INPUT chain with rule numbers, in the order they are evaluated
$ sudo iptables -L INPUT -n -v --line-numbers

# What ufw itself manages
$ sudo ufw status verbose

# NAT rules, which is where Docker's port publishing lives
$ sudo iptables -t nat -L -n -v

If a rule is not doing what you expect, find where it sits compared to the rule that overrides it. Do not assume the tool that last printed “Rule added” is the one in charge.

Quick summary

  • ufw is a frontend for iptables, not a separate firewall. Both write to the same chains.
  • Netfilter matches top to bottom and the first match wins. Position decides, not the tool.
  • iptables -A adds to the end, so ufw’s rules win. iptables -I INPUT 1 adds to the top, so your rule wins and ufw status becomes misleading.
  • Docker is the most common trigger. Its NAT rules bypass ufw, so use the DOCKER-USER chain.
  • When unsure, read the real rules with iptables -L -n --line-numbers.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *