Skip to content
Hugo Méndez
  • Docker
  • Linux
  • Security
  • DevOps

Docker and firewalld: why your "closed" port is still open (and what to do)

Docker publishes ports around firewalld and ufw rules. How to check it, how to expose only what you need, the 127.0.0.1 trick and Compose override pitfalls with duplicated ports.

Hugo MéndezMay 26, 20262 min read

A classic server scare: you lock down the firewall to 80 and 443, start docker compose, and your database or internal API is reachable from the internet. The firewall is not broken; that is how Docker works.

Why it happens

When you publish a port (ports: - "5432:5432"), Docker adds its own networking rules to route that traffic to the container. That traffic is routed before the input rules you configured with firewalld or ufw are evaluated, so your “deny” rule never applies.

Test from another machine, not from the server itself:

nc -zv SERVER_IP 5432

Golden rule: only publish what must be public

Most services (databases, internal APIs, Redis) don’t need published ports. Containers on the same Compose network talk by service name:

services:
  api:
    environment:
      MONGO_URI: mongodb://mongo:27017/app
  mongo:
    image: mongo:7
    # no "ports": unreachable from outside Docker's network

If a reverse proxy running on the host needs to reach a service, publish it on loopback only:

ports:
  - "127.0.0.1:3000:3000"

When 127.0.0.1 doesn’t work

On some RHEL-family servers with firewalld, loopback-published ports can become unreachable even from the host. Check:

  1. Did you reload firewalld after starting Docker? A reload can wipe Docker’s rules. Restart Docker afterwards: sudo firewall-cmd --reload && sudo systemctl restart docker.
  2. Is Docker’s userland proxy disabled in /etc/docker/daemon.json? Loopback traffic then depends on other kernel settings.
  3. Cleanest alternative: publish nothing and run the proxy (Nginx, Caddy, Traefik) as a container on the same network. Only the proxy publishes 80 and 443.

“Port is already allocated” with override files

With a base docker-compose.yml plus a docker-compose.prod.yml, port lists are merged, not replaced, so you end up binding the same port twice. Since Compose v2.24 you can replace the list:

services:
  frontend:
    ports: !override
      - "127.0.0.1:8100:80"

Checklist

  • Internal services publish no ports.
  • Services behind a host proxy are published on 127.0.0.1.
  • Verified from outside the server with nc or nmap.
  • Docker restarted after firewall changes.
  • Production overrides use !override for ports.

Not sure what your production server exposes? I can audit it.

Back to blog

Related services

Got a process that has outgrown spreadsheets?

Tell me what you need. Within one business day I will reply with a clear idea of how to solve it and what it costs.