UFW (Uncomplicated Firewall) is a frontend for iptables. It's not the most powerful tool, but it's fast to configure, easy to audit, and doesn't require memorizing iptables syntax at 2am when something breaks.

The main danger: enabling UFW before allowing SSH. You lock yourself out and need console access to fix it. The order below avoids that.

Check if UFW is installed

sudo ufw status

Should be inactive on a fresh Ubuntu server. If it's already active and you don't know what rules are set, check before touching anything:

sudo ufw status verbose

The safe enable sequence

# 1. Allow SSH first — before enabling anything
sudo ufw allow ssh

# 2. Now enable
sudo ufw enable

That's it. Don't enable first and add SSH second. I've seen people do it, I've done it myself once — you have about 30 seconds before the existing session times out and you're locked out.

If you're on a non-standard SSH port:

sudo ufw allow 2222/tcp

Default policies

UFW starts with defaults: deny all incoming, allow all outgoing. That's the right starting point. Confirm:

sudo ufw default deny incoming
sudo ufw default allow outgoing

Common rules

# Web server
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Or use the Nginx profile (if installed):
sudo ufw allow 'Nginx Full'

# Specific port
sudo ufw allow 8080/tcp

# Specific IP only (e.g. office access to a management port)
sudo ufw allow from 203.0.113.42 to any port 8080

# Specific subnet
sudo ufw allow from 10.0.0.0/24

# Allow a port range
sudo ufw allow 6000:6100/tcp

Application profiles

UFW has pre-defined profiles for common services. See what's available:

sudo ufw app list

Common ones: OpenSSH, Nginx Full, Nginx HTTP, Nginx HTTPS. Using profiles is cleaner than raw ports because the name documents the intent when you read the rules back later.

View and manage rules

# List rules with numbers
sudo ufw status numbered

# Delete by number
sudo ufw delete 3

# Delete by rule definition
sudo ufw delete allow 8080/tcp

Deleting by number is more reliable when you have multiple similar rules. Always list numbered before deleting.

UFW and Docker: the problem

Docker bypasses UFW by manipulating iptables directly. If you run docker run -p 8080:80 nginx, port 8080 is publicly accessible even if UFW has no rule for it.

This is a known Docker behavior that surprises everyone the first time. Three options:

Option 1 — Only bind to localhost in Docker (my preference for containers behind Nginx):

docker run -p 127.0.0.1:8080:80 nginx

The container is only accessible from the host itself. Nginx proxies to it. UFW is irrelevant because nothing external reaches Docker directly.

Option 2 — Use Docker's built-in network controls: define networks in compose files and don't expose ports that don't need to be public.

Option 3 — Disable Docker's iptables manipulation (complex, breaks container networking if you're not careful): add "iptables": false to /etc/docker/daemon.json. Only do this if you know what you're replacing it with.

Rate limiting with UFW

UFW has a built-in rate limit rule that bans IPs making more than 6 connections in 30 seconds:

sudo ufw limit ssh

It's a blunt instrument compared to fail2ban, but if you're not running fail2ban it's better than nothing. If you have fail2ban already set up, you don't need this.

Logging

# Enable logging
sudo ufw logging on

# Check what's being blocked
sudo tail -f /var/log/ufw.log

Log levels: low (blocked only), medium, high, full. Default is low. I keep it at low — medium and above on a busy server generates a lot of noise.

Reset everything

If you've made a mess and want to start over:

sudo ufw reset

Disables UFW and deletes all rules. Then follow the safe enable sequence above.

Quick reference: my standard setup for a web server

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose

Six commands. Done.


Related posts