UFW Firewall Rules Every Server Should Have
07 Nov 2025UFW (Uncomplicated Firewall) is a friendly wrapper around iptables/nftables, and “uncomplicated” is accurate — you can have a sane baseline running in under two minutes. The catch is that the two-minute version is also how people lock themselves out of a fresh VPS.
The one rule that matters most
Before you enable UFW, allow SSH. If you enable the firewall first and SSH isn’t explicitly allowed, your next connection attempt just times out — and unless your provider gives you a web-based console, you’ve lost access to the box entirely.
sudo ufw allow OpenSSH
sudo ufw enable
If you’ve moved SSH off port 22, allow that port number instead: sudo ufw allow 2222/tcp.
The baseline rule set
For a typical web server — reverse proxy or app serving HTTP/HTTPS directly:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
default deny incoming is what makes this a real firewall rather than a suggestion — everything not explicitly allowed is blocked. default allow outgoing lets the server make its own outbound connections (pulling Docker images, hitting external APIs) without you having to allowlist every destination.
Check what’s active:
sudo ufw status verbose
Rules for common self-hosting setups
Database you only access from the app server, not the public internet:
sudo ufw allow from 10.0.0.5 to any port 5432
This allows Postgres traffic only from that specific IP, not the world. Never ufw allow 5432 on a database box with a public IP — that’s an open invitation, and automated scanners check for exposed database ports constantly.
Docker Swarm nodes talking to each other:
sudo ufw allow from 10.0.0.0/24 to any port 2377
sudo ufw allow from 10.0.0.0/24 to any port 7946
sudo ufw allow from 10.0.0.0/24 to any port 4789/udp
Swarm needs these ports open between cluster nodes for management, discovery, and overlay networking. Scope them to your private subnet, not the public internet.
Temporary access for debugging, auto-expiring is not built in — UFW doesn’t do time-limited rules natively, so remove debug rules manually when you’re done:
sudo ufw delete allow 8080/tcp
The gotcha with Docker
This one catches almost everyone eventually: Docker bypasses UFW by default. Docker manipulates iptables directly to publish container ports, and it does this at a level that sits in front of UFW’s rules. You can ufw deny 5432 and still find your Dockerized Postgres reachable from the internet, because Docker’s own NAT rules already forwarded the traffic before UFW’s chain gets a say.
The practical fix: don’t publish ports you don’t need to the host at all. If a container only needs to be reached by other containers, use a Docker network and skip the -p port mapping entirely. If you do need to expose a port, bind it to localhost only unless it truly needs to be public:
ports:
- "127.0.0.1:5432:5432"
That way, even with Docker’s iptables rules in play, the port is only reachable from the host itself, not the outside network — UFW becomes almost irrelevant for that port because nothing outside the box can reach it either way.
The takeaway
UFW is genuinely simple for the 90% case: deny incoming, allow SSH and your web ports, done. Where it gets people is Docker’s port publishing sidestepping it silently — verify with sudo iptables -L -n | grep DOCKER or just test from an external machine (curl your VPS IP on the port in question) rather than trusting ufw status alone once Docker’s involved.