
Verdict Cepat / TL;DR: Setting up Uncomplicated Firewall (UFW) on your Linux VPS protects your open ports from port scanners and unauthorized SSH login attempts. However, Docker silently bypasses default UFW rules by modifying raw iptables chains directly. To build a secure production server, you must default-deny incoming traffic, allow essential ports (22, 80, 443), and apply iptables-nft fixes or ufw-docker rules to stop containers from exposing internal ports directly to the public internet.
When you launch a fresh VPS on platforms like DigitalOcean, Linode, or AWS EC2, your server is assigned a public IPv4 address that gets pinged by automated botnets within minutes. Leaving every port wide open means any internal service listening on localhost or 0.0.0.0 (like Redis, PostgreSQL, or an unauthenticated admin dashboard) becomes immediately accessible to anyone scanning your IP.
While cloud providers offer network security groups, a host-based firewall on the server itself provides an essential defense-in-depth layer. Uncomplicated Firewall (UFW) serves as the standard user-friendly wrapper for iptables and nftables on Ubuntu and Debian distributions. It replaces complex networking rules with clear, readable commands.
However, running UFW on modern servers involves a critical pitfall: Docker’s default networking behavior silently bypasses UFW rules. When Docker binds container ports, it inserts rules into iptables before UFW’s filter chain executes. This guide breaks down step-by-step UFW configuration, port hardening, and the exact fix for the hidden Docker security loophole.
Saya have spent years managing cloud infrastructure, and I can tell you that an unconfigured firewall is one of the easiest vulnerabilities to exploit. Saya always enforce strict default-deny policies on every fresh instance. Saya recommend treating host firewall setup as mandatory before installing any production software stack.
Before configuring UFW firewall rules, confirm that your environment matches these requirements:
sudo privileges or direct root SSH access.By default, UFW is installed on Ubuntu servers but remains inactive. Check its current operational status by running:
sudo ufw status verboseIf UFW reports Status: inactive, do not enable it yet! Enabling UFW before allowing your SSH port will immediately lock you out of your remote server session.
First, configure the baseline default policies. A secure firewall configuration denies all incoming connection attempts while allowing all outgoing traffic from your server:
sudo ufw default deny incoming
sudo ufw default allow outgoingThese two commands guarantee that any service or port you do not explicitly open will block incoming requests by default.
Now, explicitly whitelist the ports required for server administration and web hosting. To maintain your active SSH connection, allow port 22 (or your custom SSH port) before turning the firewall on:
sudo ufw allow 22/tcpIf your SSH daemon uses a custom port such as 2222, allow that port instead:
sudo ufw allow 2222/tcpNext, allow standard HTTP and HTTPS web traffic for web servers like Nginx, Apache, or reverse proxies like Nginx Proxy Manager:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpAlternatively, you can use application profile names registered with UFW:
sudo ufw allow "Nginx Full"
sudo ufw allow OpenSSHWith SSH and web ports safely allowed in the rule queue, turn on UFW:
sudo ufw enableThe terminal will prompt you with a warning that command evaluation may disrupt existing SSH connections. Type y and press Enter. To verify your rules are active, inspect the numbered status list:
sudo ufw status numberedYou will see output structured like this:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 22/tcp (v6) ALLOW IN Anywhere (v6)
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6)
[ 6] 443/tcp (v6) ALLOW IN Anywhere (v6)For administrative services such as database engines, Redis, or internal web dashboards, opening ports to the public internet is risky. UFW lets you restrict access to specific static IP addresses or subnet blocks.
To restrict database access (e.g., PostgreSQL port 5432) so that only your home office IP (e.g., 203.0.113.45) can connect, run:
sudo ufw allow from 203.0.113.45 to any port 5432 proto tcpIf you operate a cluster of VPS instances on a private VPC network (e.g., 10.136.0.0/16), grant full internal communication across all ports with one rule:
sudo ufw allow from 10.136.0.0/16If you made a typo or no longer need a port open, view the rule numbers with sudo ufw status numbered and delete the target rule by index:
sudo ufw delete 3Many system administrators assume that running sudo ufw default deny incoming keeps all unlisted ports safe. However, when you launch a Docker container with published ports, such as docker run -d -p 8080:80 nginx, Docker modifies Linux iptables directly in the PREROUTING chain of the nat table.
Because Docker places its routing rules before the ufw-user-input chain, external traffic to port 8080 hits the Docker container directly, completely ignoring your UFW default deny policy. Even if UFW shows port 8080 as blocked or unlisted, port scanners can still access your container service worldwide.
To restore UFW control over container traffic without breaking internal Docker bridge networking, choose one of these two industry-standard fixes:
If your containers are routed through a local reverse proxy (like Nginx, Caddy, or Nginx Proxy Manager), do not publish container ports to 0.0.0.0. Instead, bind them explicitly to the local loopback interface 127.0.0.1 in your CLI commands or docker-compose.yml files:
ports:
- "127.0.0.1:8080:80"Because the container only listens on loopback, external users on public interfaces cannot reach it, while your local Nginx proxy can route requests safely.
ufw-docker Security RulesIf you need Docker containers exposed selectively on public interfaces, use the open-source ufw-docker patch. This utility modifies /etc/ufw/after.rules so UFW evaluates incoming packets before forwarding them to Docker’s DOCKER-USER iptables chain.
Append the following rule block to the end of /etc/ufw/after.rules:
# BEGIN UFW AND DOCKER FIX
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --dport 53 -j RETURN
-A DOCKER-USER -p tcp -m tcp --dport 53 -j RETURN
-A DOCKER-USER -j ufw-docker-logging-deny
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER FIXAfter editing the file, reload UFW to apply the patch:
sudo ufw reloadWith this patch active, you can allow public access to a specific Docker container (e.g., container web-app on port 80) using simple UFW syntax:
sudo ufw route allow proto tcp from any to any port 80Understanding where UFW fits in your overall architecture helps you design better server defenses. The table below compares host firewalls with network security groups.
| Feature | UFW (Host Firewall) | Raw iptables / nftables | Cloud Security Groups |
|---|---|---|---|
| Location | Inside OS kernel | Inside OS kernel | Cloud hypervisor / network boundary |
| Ease of Use | Very High (Simple CLI) | Low (Complex syntax) | High (Web GUI / Terraform) |
| Docker Integration | Bypassed by default (Requires patch) | Directly managed by Docker daemon | Unaffected by Docker (Operates outside OS) |
| Resource Usage | Negligible | Negligible | Zero host CPU/RAM usage |
| Performance Impact | Sub-millisecond filtering | Sub-millisecond filtering | Filtered before reaching host NIC |
| Best Purpose | Internal host protection & rule simplicity | Custom packet manipulation & NAT | First line of network perimeter defense |
If you encounter unexpected connectivity loss or blocked services, work through these diagnostic checks:
sudo ufw allow 22/tcp && sudo ufw reload.127.0.0.1 or 0.0.0.0 using sudo netstat -tulpn | grep LISTEN. If UFW blocks inter-container communication, check if internal Docker bridge interfaces are allowed.sudo ufw logging on and monitor incoming blocked packets in real time with sudo tail -f /var/log/ufw.log.Configuring UFW on a Linux VPS provides an instant security boost against unauthorized scanning and exploit attempts. By setting default deny rules, whitelisting administrative IPs, and fixing Docker’s default iptables bypass, your server remains locked down and production-ready.
Building out a complete VPS stack? Check out our step-by-step tutorial on how to set up Nginx Proxy Manager on a VPS or read our hands-on guide on setting up Docker on a VPS with Portainer to streamline your container deployment workflow. You can also explore vendor documentation at Ubuntu Official Firewall Documentation, Docker Firewall Docs, DigitalOcean UFW Guide, and Debian Firewall Reference.