CentOS Stream / Rocky Linux: UFW Blocking Docker Container Port Exposures
Troubleshoot and resolve UFW firewall conflicts preventing Docker containers from exposing ports on CentOS Stream or Rocky Linux systems. Reclaim access.
Troubleshoot and resolve UFW firewall conflicts preventing Docker containers from exposing ports on CentOS Stream or Rocky Linux systems. Reclaim access.
CentOS Stream / Rocky Linux: UFW Blocking Docker Container Port Exposures
When deploying containerized applications with Docker on CentOS Stream or Rocky Linux, it's common to use a firewall to secure the host system. While firewalld is the native solution, some administrators might opt for UFW (Uncomplicated Firewall) for its perceived simplicity. However, UFW's default configuration can often clash with Docker's internal networking, leading to frustrating scenarios where your Docker containers appear to be running correctly, but their exposed ports are inaccessible from outside the host. This guide will walk you through diagnosing and resolving these conflicts, restoring connectivity to your containerized services.
Symptom & Error Signature
The primary symptom is a lack of external connectivity to a Docker container's exposed port, even though docker ps indicates the port is mapped and the container is running.
Typical observations include:
- Browser/Client:
- "Connection Refused"
- "ERR_CONNECTION_TIMED_OUT"
- "Site cannot be reached"
curlfrom an external machine:curl: (7) Failed to connect to <your_server_ip> port <exposed_port> after XXX ms: Connection refusedcurlfrom the Docker host itself (often works):# This might work if accessing via localhost, confirming the service is running curl http://localhost:<exposed_port>- Docker Container Logs: The container's logs (e.g.,
docker logs <container_id>) usually show no errors related to networking or port binding, as the issue lies with the host's firewall. ssornetstatoutput:
This indicates# On the Docker host, showing docker-proxy listening on the exposed port sudo ss -tulpn | grep <exposed_port> # Example output for a container exposing port 8080: # tcp LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=12345,fd=4))docker-proxyis correctly listening, but external access is blocked by the host firewall.
Root Cause Analysis
The conflict between UFW and Docker stems from how both tools manage iptables rules on the Linux host.
Docker's
iptablesManagement: When you expose a port from a Docker container (e.g.,-p 8080:80), Docker creates a set ofiptablesrules. These rules perform Network Address Translation (NAT) to redirect incoming traffic from the host's exposed port to the container's internal IP and port. It also creates rules in theFORWARDchain to allow this traffic. Docker typically inserts its rules into specific chains likeDOCKERandDOCKER-USER.UFW's
iptablesManagement: UFW is a user-friendly frontend foriptables. By default, UFW often sets itsDEFAULT_FORWARD_POLICYtoDROPin/etc/default/ufw. This means any traffic that is forwarded through the host (which includes traffic destined for Docker containers) will be blocked unless explicitly allowed by UFW rules.The Conflict: The core problem is rule order and chain traversal. UFW often places its
DROPrules very early in theFORWARDchain of thefiltertable. Docker's rules, while present, may be jumped to after UFW has already decided toDROPthe packet. This prevents external traffic from ever reaching theDOCKERorDOCKER-USERchains where the allow rules for your containers reside.CentOS/Rocky Linux Specifics: While UFW is not native to CentOS/Rocky (which uses
firewalld), users who install UFW on these systems might inadvertently create more complexiptablesinteractions iffirewalldisn't fully disabled or if they are unfamiliar with theiptablesstructure on these distributions. The fundamental UFW-Docker conflict, however, remains consistent across distributions.
Step-by-Step Resolution
There are several ways to resolve this, ranging from simple but less secure, to more integrated and robust. We'll start with the recommended approach for UFW users.
#### 1. Initial Diagnostics & Verification
Before making changes, confirm UFW is active and Docker is running:
# Check UFW status (look for "Status: active" and "Default: deny (forward)")
sudo ufw status verbose
# Expected output example:
# Status: active
# Logging: on (low)
# Default: deny (incoming), deny (outgoing), deny (forward)
# New profiles: skip
# Check Docker container status and port mapping
docker ps
# Confirm the service is listening on the host (e.g., for port 8080)
sudo ss -tulpn | grep 8080
# Attempt external connection from another machine (or via curl <your_server_ip>:<port>)
# This should fail if the issue persists
curl -v http://<YOUR_SERVER_IP>:<CONTAINER_PORT>
#### 2. Method A: Recommended Docker/UFW Integration (More Secure)
This method involves adjusting UFW's iptables rules to ensure Docker's traffic is processed before UFW's default DROP policy on forwarded packets. This allows Docker to manage its own port forwarding rules effectively without compromising overall firewall security.
Edit UFW's
after.rules: UFW usesbefore.rulesandafter.rulesto applyiptablesrules before or after UFW's automatically generated rules. We need to insert rules that prioritize Docker'sFORWARDrequirements.Open the
after.rulesfile for editing:sudo vim /etc/ufw/after.rules # If you also use IPv6, repeat for: sudo vim /etc/ufw/after6.rulesAdd Docker-specific
iptablesRules: Locate the*filtersection. Add the following rules before theCOMMITline, ideally at the very end of the*filtersection. These rules explicitly allowFORWARDtraffic that Docker needs.# BEGIN UFW AND DOCKER INTEGRATION *filter :ufw-user-forward - [0:0] # Ensure this chain exists # Allow Docker's internal network to reach the host and the outside world -A FORWARD -i docker0 -j ACCEPT # Allow traffic from anywhere to Docker's internal network (docker0) # This ensures external requests reaching docker-proxy are forwarded -A FORWARD -o docker0 -j ACCEPT # Allow established/related connections through the firewall -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # Jump to the DOCKER-USER chain, allowing Docker to insert its own allow rules -A FORWARD -j DOCKER-USER COMMIT # END UFW AND DOCKER INTEGRATIONThese rules must be added within the
*filtersection and before theCOMMITstatement. TheDOCKER-USERchain is where Docker's dynamiciptablesrules for exposed ports are typically inserted. By jumping to it here, we ensure UFW allows that traffic.Adjust
DEFAULT_FORWARD_POLICY(Optional, but often necessary if issues persist): If the above changes don't fully resolve the issue, UFW'sDEFAULT_FORWARD_POLICYmight still be too restrictive. While the rules inafter.rulesshould ideally precede the default policy,iptablesrule processing can be complex.Open
/etc/default/ufw:sudo vim /etc/default/ufwChange the line:
- DEFAULT_FORWARD_POLICY="DROP" + DEFAULT_FORWARD_POLICY="ACCEPT"Setting
DEFAULT_FORWARD_POLICY="ACCEPT"significantly lowers your host's default forwarding security. It means all traffic being routed through your server is allowed by default. This is generally discouraged for maximum security. Only use this if the more granular rules above do not work, and ensure you understand the security implications. If you set this toACCEPT, the rules inafter.rulesare even more critical to manage what is forwarded, but the default "drop everything" fallback is removed.Reload UFW and Restart Docker: For the changes to take effect, UFW needs to be reloaded, and then Docker should be restarted to ensure it rebuilds its
iptablesrules with the new UFW context.sudo systemctl restart ufw sudo systemctl restart dockerTest Connectivity: Attempt to access your Docker container's exposed port from an external machine again.
curl -v http://<YOUR_SERVER_IP>:<CONTAINER_PORT>This should now succeed.
#### 3. Method B: Open Specific Ports in UFW (Simple but Manual)
If you only have a few Docker containers with static port exposures, you can simply open the required ports in UFW for the host. This is less dynamic but straightforward.
# Allow TCP traffic on port 8080 (example)
sudo ufw allow 8080/tcp
# Allow TCP traffic on port 443 (example)
sudo ufw allow 443/tcp
# Reload UFW to apply changes
sudo ufw reload
# Restart Docker (optional, but good practice after firewall changes)
sudo systemctl restart docker
This method primarily opens ports in the
INPUTchain, allowing traffic to the host. Whiledocker-proxylistens on the host and Docker's NAT rules handle the redirect, theFORWARDchain interaction is still critical. If this method doesn't work consistently, Method A is likely required as it directly addresses theFORWARDchain conflict.
#### 4. Method C: Switch to firewalld (Native for CentOS/Rocky Linux)
For CentOS Stream and Rocky Linux, firewalld is the default and recommended firewall management tool. It often integrates more smoothly with Docker without requiring manual iptables rule manipulation. If you're encountering persistent issues with UFW, consider migrating.
Disable UFW:
sudo ufw disable sudo systemctl stop ufw sudo systemctl disable ufwEnable and Start
firewalld:sudo systemctl start firewalld sudo systemctl enable firewalld sudo firewall-cmd --state # Expected output: runningAdd Docker to
firewalld(or allow specific ports): Docker usually creates its ownfirewalldzone (docker) and manages its rules whenfirewalldis active during Docker service start. If not, you might need to add rules manually.# Option 1: Reload firewalld and restart Docker (often sufficient) # Docker automatically adds rules to firewalld if firewalld is active upon Docker start. sudo systemctl reload firewalld sudo systemctl restart docker # Verify if Docker has added its rules sudo firewall-cmd --get-active-zones # Look for 'docker0' or similar interfaces under a zone like 'docker' # Option 2: Manually add specific ports to the public zone (example for 8080/tcp) sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent sudo firewall-cmd --reloadAfter switching to
firewalld, ensure that any previousiptablesrules manually added by UFW (if not fully purged) do not conflict. A clean reboot after disabling UFW and enablingfirewalldcan sometimes help to clear oldiptablesstates.
Choose the method that best fits your security posture and management style. Method A provides a good balance of security and Docker integration for users who prefer UFW on CentOS/Rocky systems, while Method C offers the native and often more seamless solution.