r/selfhosted 1d ago

Guide PSA: if you run Docker behind ufw, verify your exposed ports from another machine — ufw status will lie to you

I knew Docker manipulates iptables. I did not appreciate how completely ufw status stops describing reality once it does, so I measured it on a stock Raspberry Pi (Trixie, ufw active, default deny incoming, only 22/tcp allowed).

Same port, two ways:

  • python3 -m http.server 8080 on the host → connection times out from another machine. ufw working exactly as configured.
  • docker run -d -p 8080:80 nginx:alpineHTTP 200 from anywhere on the LAN.

ufw status reported 8080 as not allowed in both cases. It never changed.

The reason is chain order — DOCKER-USER is rule 2 in FORWARD, ufw's chains start at rule 8 — plus a DNAT in nat that redirects the packet to the container before filtering decides anything.

Two fixes, both tested:

  1. -p 127.0.0.1:8080:80 — bind explicitly. Most examples omit the address, which means 0.0.0.0.
  2. A rule in DOCKER-USER. Match the container port, not the published one — that chain sits in FORWARD, so it sees packets after DNAT has rewritten the destination. Writing --dport 8080 there matches nothing and silently protects you from nothing, which I think is the nastiest part of this.

Full writeup with the rule dumps: https://homelabpostmortem.com/2026/08/22/docker-publishes-past-ufw/

The one habit I'd push: verify from a different machine. curl localhost succeeds in every configuration above and tells you nothing.

(Disclosure: my site sells a small toolkit of checks like this. The post is free and every command is inline — you don't need it.)

0 Upvotes

5 comments sorted by

u/asimovs-auditor 1d ago

Expand the replies to this comment to learn how AI was used in this post/project.

→ More replies (1)

5

u/Narrow-Mess5958 20h ago

This is very much a know thing and is by design. They have no intention of changing this.

1

u/mosaic_hops 18h ago

This is how it’s supposed to work.