UFW and Docker on the Same VPS Without Breaking Your Apps
Learn how to use UFW on a Docker host without accidentally exposing containers or locking yourself out, and build a safer default firewall pattern for self-hosted services.
How UFW, Docker, published ports, reverse proxies, and iptables interact on a real VPS.
Docker Compose hosts, Ubuntu or Debian VPS deployments, and operators tightening network exposure.
Docker can bypass the firewall behavior beginners expect if ports are published carelessly.
Before you begin
- Confirm you already have working SSH access before changing firewall rules.
- Know which ports should be public, private, or localhost-only.
- Be ready to test from outside the server, not only from inside the shell.
UFW is a good simple firewall layer, but Docker changes the picture because it manages iptables rules of its own. That is where many beginners get confused. They enable UFW, publish a container port, and then discover traffic is still getting through in ways they did not expect. The right goal is not memorizing every iptables detail. It is understanding where Docker changes the path so you can keep your exposure deliberate.
Why UFW and Docker surprise people
When you publish a port with docker run -p or a Compose ports: block, Docker usually adds NAT and filter rules directly through iptables. That can make a published container reachable even when your mental model says UFW should have blocked it. This is why guides that say “just use UFW” are incomplete on Docker hosts. The correct guide must explain that published ports, not container existence alone, are what create unexpected exposure.
ufw deny 8080 automatically protects a Docker-published port the way you expect. Test it. Docker’s rules can change the result.Decide what should be public and what should stay private
Start by classifying services. SSH should usually stay narrowly controlled. Nginx or another reverse proxy may need public access on ports 80 and 443. Many app containers should not be public at all. A strong default is to keep apps on internal Docker networks and expose only the reverse proxy. If an app only needs local access, bind it to 127.0.0.1 instead of all interfaces.
This is the most practical habit in the whole guide. You reduce firewall complexity dramatically when fewer things are public in the first place.
Build a safe default UFW policy
Set your default inbound policy to deny, then explicitly allow only what the machine truly needs. A typical small web VPS might allow SSH, HTTP, and HTTPS, while everything else stays blocked unless a specific service requires more.
Useful commands to verify during setup include:
sudo ufw status verbosedocker pssudo iptables -L -n -vsudo iptables -t nat -L -n -v
If you are using IPv6, make sure your UFW configuration reflects that intentionally. Ignoring IPv6 while thinking only about IPv4 is a common self-hosting mistake.
Control Docker exposure properly
On Docker hosts, the main pattern to understand is that the DOCKER-USER chain gives you a place to apply filtering before Docker’s own acceptance rules finish the job. That makes it useful for adding host-level restrictions to published container traffic. Another safe pattern is even simpler: do not publish app ports at all unless you must. Put Nginx in front, keep app containers private, and let only the reverse proxy face the public internet.
If you do need published ports, verify exactly which interface they bind to. Binding to 127.0.0.1:PORT:PORT is much safer than binding to every interface by default. Also be careful with advice that disables Docker’s iptables management entirely. That is an advanced redesign, not a beginner-safe default.
Verify after every change
After enabling or updating rules, test from both inside and outside the server. Confirm the ports you expect are reachable and the ones you expect to stay private are truly blocked. Re-test after container restarts too. A firewall rule that only works until the next restart is not a finished solution.
Good verification includes checking service reachability, published ports, and whether your reverse proxy still behaves normally after firewall changes.
Common mistakes to avoid
The biggest mistakes are enabling UFW before allowing SSH, assuming UFW alone controls Docker-published ports, forgetting IPv6, publishing every app directly to the internet, and adding rules to the wrong iptables chain. Another classic mistake is feeling safe because commands looked correct without ever testing from an outside client.
What to do next
Once your firewall and published ports are under control, the next useful step is understanding the reverse-proxy side when something breaks. Continue with How to Debug a 502 Bad Gateway in Nginx.
