Linux Server Security is the current field-guide cluster.Linux • Security • Self-hosting • Practical tools
Linux Server Security

Docker and UFW: Why Published Ports Can Surprise Linux Server Operators

UFW is useful, but Docker-published ports can still surprise Linux server operators. On Linux, Docker creates firewall rules for bridge networks and port publishing. Docker’s documentation says Docker and UFW use firewall rules in ways that make them incompatible for published container traffic, because packets can be routed before UFW’s ordinary INPUT and OUTPUT chains apply.

The safe response is not panic. The safe response is to audit what Docker publishes, bind private apps to localhost, and treat UFW as one layer rather than the only exposure control.

The short rule

Do not assume this means a Docker-published port is private:

sudo ufw status

Also check Docker and listening sockets:

docker ps --format 'table {{.Names}}\t{{.Ports}}'
ss -ltnp

If Docker shows 0.0.0.0:PORT->..., treat that port as intentionally public or fix it.

Why UFW can be misleading with Docker

UFW is a friendly frontend for Linux firewall rules. Docker also manages firewall rules so container networking, NAT, and published ports work.

Docker’s firewall documentation explains that published container traffic is handled in the nat table and can be diverted before it reaches the chains UFW normally uses. The result: a port may be reachable even when a simple reading of UFW rules makes you think it should not be.

This is especially important on Ubuntu VPS machines where UFW is common and Docker is installed later.

The safer design pattern

For admin tools and internal apps, avoid direct public Docker publishes.

Prefer this:

services:
  app:
    image: example/app:latest
    ports:
      - "127.0.0.1:8080:80"

Then put one intentional access layer in front:

Avoid this unless the app should be directly reachable from the network:

services:
  app:
    image: example/app:latest
    ports:
      - "8080:80"

The second form can publish to all host interfaces by default.

Audit checklist for an existing server

Start with read-only checks.

1. List Docker-published ports

docker ps --format 'table {{.Names}}\t{{.Ports}}'

Look for lines like:

0.0.0.0:8080->80/tcp

That is the first signal to investigate.

2. Check local sockets

ss -ltnp

This shows what is listening locally. Treat 0.0.0.0:PORT as broader than 127.0.0.1:PORT.

3. Check UFW, but do not stop there

sudo ufw status verbose

UFW still matters for ordinary host services such as SSH or a reverse proxy. It just may not tell the whole story for Docker-published traffic.

4. Check from outside, only for systems you own

From a separate machine or an allowed scanner, test the public IP:

nmap -Pn -p 8080 SERVER_PUBLIC_IP

If the port is open and you did not intend it to be public, fix the Docker publish first.

Fix the common case: bind the container to localhost

If an app only needs to be reached by a local reverse proxy or tunnel, change the publish from this:

ports:
  - "8080:80"

to this:

ports:
  - "127.0.0.1:8080:80"

Then recreate the container during a maintenance window:

docker compose up -d

After it restarts, re-run:

docker ps --format 'table {{.Names}}\t{{.Ports}}'
ss -ltnp

Do not close your operational session or remove the old access path until the intended route works.

When direct Docker publishing is okay

Direct publishing can be fine for a deliberate public service, for example a web app that is meant to be exposed and has its own safe deployment path.

Even then, document:

Public by accident is the problem.

What not to do casually

Do not disable Docker firewall management on a production server without a replacement plan

Docker’s documentation warns that preventing Docker from creating firewall rules is not appropriate for most users and is likely to break container networking unless you replace the rules correctly.

Do not edit Docker-created firewall chains by hand as your first move

Docker relies on its own rules for bridge networking and published ports. Manual rule surgery can create fragile behavior that breaks during Docker restarts or package updates.

Do not rely on one view of exposure

Use Docker output, local socket output, UFW status, cloud firewall rules, and an outside-in check together.

Sources

Verification note

This page is source-checked against Docker documentation and uses read-only audit commands where possible. It does not recommend disabling Docker firewall management as a first fix. Test any Compose changes during a maintenance window and verify from a second network path you control.

Bottom line

UFW is not enough by itself to reason about Docker exposure. Check Docker’s published ports, bind private apps to localhost, and expose only the one public path you actually mean to operate.