Guideschevron_rightServers

Servers · 6 min read · Updated

UFW Not Working with Docker: Why Ports Stay Open

When you publish a container port with -p 8080:80 or a Compose ports: entry, Docker writes its own firewall rules that redirect that traffic to the container before UFW's rules are ever checked, so the port is open to the internet even though ufw status says otherwise. Docker's documentation states this directly. Fix it by publishing the port on 127.0.0.1 only and putting a reverse proxy in front, by filtering in Docker's DOCKER-USER chain, or by blocking the port in a firewall outside the server.

Why It Happens

UFW's rules sit on the path for traffic addressed to the server itself. Docker handles a published port earlier: a rule in the nat table rewrites the destination to the container's address, so the packet is now traffic passing through the server to another network, and it follows Docker's forwarding rules instead. UFW never sees it. You can see Docker's rules and what is published:

docker ps --format '{{.Names}}\t{{.Ports}}'   # 0.0.0.0:8080->80/tcp = open on every address
sudo iptables -t nat -L DOCKER -n                # the redirect rules Docker added

Always confirm from another machine, since a test from the server itself proves nothing: nc -zv SERVER_IP 8080 or curl -I http://SERVER_IP:8080.

Fix 1: Publish on 127.0.0.1 and Proxy

The cleanest fix for web apps. Publish the port on the loopback address only, so it is reachable from the server but not from outside, and let Nginx or another reverse proxy on the server handle HTTPS on 443:

# docker run
docker run -d -p 127.0.0.1:8080:80 nginx

# docker compose
services:
  app:
    image: nginx
    ports:
      - "127.0.0.1:8080:80"

Containers that only talk to each other (a database for your app, for example) need no ports: entry at all: on the same Compose network they reach each other by service name. Running Docker on a VPS sets this up from the start.

Fix 2: Filter in DOCKER-USER

Docker processes the DOCKER-USER chain before its own rules, and leaves it alone, so rules there apply to published ports. To allow outside access to containers only from one address, find the public interface with ip route get 1.1.1.1 (the name after dev), then insert two rules. The second is inserted above the first and keeps replies to the containers' own outgoing connections working:

sudo iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.5 -j DROP
sudo iptables -I DOCKER-USER -i eth0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -L DOCKER-USER -n --line-numbers

Replace eth0 and the address with your own. These rules last until reboot; save them with your distribution's method (for example the iptables-persistent package on Debian and Ubuntu). This applies to Docker's default iptables mode.

Fix 3: Block It Outside the Server

A firewall in front of the server is not affected by anything Docker does inside it. On Virteche Cloud, the firewall on the server's page in the dashboard is enforced on the host: allow only the ports you mean to expose (for example 22, 80 and 443) and a container published on 8080 stays unreachable from the internet. Use it alongside Fix 1 rather than instead of it, so a mistake in one layer is caught by the other.

For how UFW, firewalld and nftables relate to each other, see UFW vs firewalld vs iptables vs nftables.

Docker is a trademark of its owner. Virteche is not affiliated with or endorsed by it. Details about the product come from its public documentation as of the date at the top of this guide.

Common Questions

  • Why does ufw status say the port is closed when it is open?

    UFW lists its own rules, which apply to traffic addressed to the server itself. Docker redirects traffic for published ports to the container before those rules are checked, so the port is reachable no matter what UFW shows.

  • Does this also happen with firewalld?

    Docker integrates with firewalld differently: it creates a docker zone for its bridge interfaces. Published ports are still opened by Docker's own rules, so test from outside rather than trusting the firewall's listing.

  • Is it safe to set "iptables": false in Docker's daemon.json?

    It stops Docker writing firewall rules, which also stops container networking from working until you write the NAT and forwarding rules yourself. It is rarely the right fix; binding ports to 127.0.0.1 or filtering in DOCKER-USER is simpler and keeps Docker working.

  • Does Docker Compose behave the same way?

    Yes. Compose creates containers through the same Docker engine, so a ports entry such as "8080:80" is published on every address, past UFW, exactly as docker run -p would be.

The firewall on each Virteche Cloud VPS runs on the host, so ports it blocks stay blocked whatever Docker does inside the server.

See VPS plans

Keep Reading

All guides