Docker's userland proxy is a Go process that Docker starts for each published port on your server. When you run ports: "8080:80" in docker-compose.yml, Docker starts a docker-proxy process listening on host port 8080 that forwards traffic to the container's port 80.
This proxy is part of why UFW rules don't protect Docker container ports — and disabling it changes how Docker handles port forwarding in ways that matter for security.
What the userland proxy actually does
Without the userland proxy, Docker uses kernel-level NAT rules (iptables DNAT) to forward traffic from published ports to container ports. The userland proxy is a fallback for cases where kernel NAT isn't available or working correctly.
# See the userland proxy processes running: ps aux | grep docker-proxy # You'll see something like: # /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
One docker-proxy process per published port. On a busy server with many containers, this adds up to significant memory overhead — each proxy process uses ~5-10MB of RAM.
How it relates to the UFW bypass
The UFW bypass happens whether or not the userland proxy is enabled. The root cause is that Docker inserts iptables rules into the FORWARD chain, which UFW doesn't manage. Disabling the userland proxy doesn't fix the UFW bypass — it just changes which kernel mechanism Docker uses to forward traffic.
Common misconception: disabling userland-proxy does not make Docker respect UFW rules. The iptables FORWARD chain bypass happens at the kernel level regardless of whether the userland proxy is running.
Disabling the userland proxy
Some sysadmins disable the userland proxy to reduce memory usage and rely purely on kernel NAT. This is safe if your kernel supports the required iptables rules:
# /etc/docker/daemon.json:
{
"userland-proxy": false
}
sudo systemctl restart docker
Before disabling: test that your containers still receive traffic after the restart. Some network configurations (IPv6, certain VPN setups) require the userland proxy. If containers stop receiving traffic after disabling, re-enable it.
Verifying the change
# After restarting Docker with userland-proxy disabled: ps aux | grep docker-proxy # Should show no docker-proxy processes # Verify containers still receive traffic: curl http://localhost:YOUR_PORT/ # Check Docker is using iptables DNAT instead: sudo iptables -t nat -L DOCKER --line-numbers
The actual fix for Docker UFW bypass
Disabling the userland proxy doesn't solve the UFW bypass. The correct fix is to bind container ports to 127.0.0.1 so they're only accessible locally:
# In docker-compose.yml — bind to localhost only:
services:
myapp:
ports:
- "127.0.0.1:8080:80" # Not accessible from internet
# NOT: "8080:80" # This binds to 0.0.0.0 — bypasses UFW
With 127.0.0.1 binding, Docker's iptables rules only create DNAT entries for loopback traffic. External traffic from the internet can't reach the container port at all — not through UFW, not through iptables, not through the userland proxy.
Quick reference
# Check if userland proxy is enabled:
docker info | grep "Userland Proxy"
# See running proxy processes:
ps aux | grep docker-proxy
# Disable in daemon.json:
# { "userland-proxy": false }
# The real UFW fix — bind to localhost:
# ports: "127.0.0.1:HOST_PORT:CONTAINER_PORT"
Audit your UFW rules and Docker port bindings for bypass risk and missing default-deny.
Open Firewall Auditor → Docker Auditor →