UFW (Uncomplicated Firewall) is the default firewall management tool on Ubuntu. It’s a frontend to iptables that makes common firewall tasks manageable without having to write iptables rules directly. I use it on most of my Ubuntu servers — it handles the 95% case well and doesn’t get in the way.
Installation and Initial State
UFW comes pre-installed on Ubuntu. Check if it’s active:
ufw status
If it says “inactive”, it’s installed but not enabled. Before enabling it, set up your rules — otherwise you might lock yourself out.
Default Policies
Always start by setting the defaults:
ufw default deny incoming
ufw default allow outgoing
This blocks all incoming traffic by default and allows all outgoing. The right starting point for most servers. You then selectively allow what you need.
Allowing Services
Allow SSH before enabling — this is important:
ufw allow ssh # Same as: ufw allow 22/tcp
If you’ve changed SSH to a non-standard port:
ufw allow 2222/tcp
Common services:
ufw allow http # Port 80
ufw allow https # Port 443
ufw allow 'Nginx Full' # Both 80 and 443
ufw allow 5432/tcp # PostgreSQL
ufw allow 6379/tcp # Redis
ufw allow 'Nginx Full' uses UFW’s application profiles (stored in /etc/ufw/applications.d/). Other profiles you might have: Apache, Apache Full, OpenSSH.
List available profiles:
ufw app list
ufw app info 'Nginx Full'
Enabling UFW
After setting up your rules:
ufw enable
You’ll get a warning: “Command may disrupt existing ssh connections.” If you’ve allowed SSH, this is fine.
ufw status verbose
This shows your rules in detail — allowed ports, protocols, and directions.
Allowing Specific IPs
Allow a single IP to access any port:
ufw allow from 203.0.113.42
Allow a specific IP to access a specific port:
ufw allow from 203.0.113.42 to any port 5432
Allow a subnet:
ufw allow from 10.0.0.0/8
This is useful for allowing internal network access to services like databases that shouldn’t be publicly exposed.
Restricting to Specific Ports/Protocols
UDP only:
ufw allow 51820/udp # WireGuard
TCP only:
ufw allow 443/tcp
Port ranges:
ufw allow 6000:6005/tcp
Rate Limiting
UFW has built-in rate limiting:
ufw limit ssh
This allows SSH but limits connections to 6 per 30 seconds per IP. Good for SSH as a quick alternative to Fail2ban (though Fail2ban is more flexible and configurable).
Viewing and Managing Rules
List rules with numbers:
ufw status numbered
Delete a rule by number:
ufw delete 3
Delete a rule by specification:
ufw delete allow 8080
ufw delete allow from 203.0.113.42 to any port 5432
Disabling and Resetting
Disable UFW (removes all rules from the kernel, doesn’t delete them):
ufw disable
Reset to defaults (deletes all rules):
ufw reset
Logging
UFW can log blocked connections:
ufw logging on # Enable logging (low level)
ufw logging medium # More verbose
ufw logging high # Very verbose
Logs appear in /var/log/ufw.log or via syslog. Useful for debugging — if something can’t connect, check the UFW log to confirm it’s being blocked.
Turn off logging when you don’t need it — it can generate a lot of data on active servers.
UFW with Docker
This is where UFW trips people up. Docker modifies iptables directly, bypassing UFW rules. If you have Docker installed and run a container with -p 8080:80, Docker will make port 8080 publicly accessible even if UFW has no rule for it.
The Docker documentation acknowledges this. To prevent it, edit /etc/docker/daemon.json:
{
"iptables": false
}
But disabling Docker’s iptables management breaks Docker networking. A better solution is to only bind Docker ports to localhost and use Nginx as a reverse proxy:
docker run -p 127.0.0.1:3000:3000 myapp
Then allow port 80/443 in UFW for Nginx, not the app directly. The app is only accessible via the reverse proxy.
Or use the ufw-docker helper that adds Docker-aware UFW rules.
Checking What’s Actually Listening
Before writing UFW rules, check what ports are actually open on your system:
ss -tlnp # TCP listening ports
ss -ulnp # UDP listening ports
This shows what’s actually running, what ports it’s using, and what interface it’s bound to. UFW should match what you actually want to expose.
Production Workflow
My typical setup on a fresh server:
# Defaults
ufw default deny incoming
ufw default allow outgoing
# SSH (first!)
ufw allow ssh
# Web server
ufw allow 'Nginx Full'
# Enable
ufw enable
# Verify
ufw status verbose
That’s the base. Then I add specific rules as needed: database access from specific IPs, monitoring ports, VPN, etc.
Check the rules before enabling, keep an SSH session open while enabling, and test from a separate connection before closing the current one. These three steps prevent the classic lockout scenario.
UFW is a solid tool. It’s not the most powerful firewall management system, but it’s simple enough that I actually understand all my rules, and that matters more than having every possible feature available. For servers where I need more complex rules — asymmetric policies, connection tracking, more granular logging — I use iptables directly or nftables. For most servers, UFW is all I need.
For servers with sensitive internal services like PostgreSQL, combining UFW with the Linux server hardening checklist gives a solid baseline security posture.