UFW (Uncomplicated Firewall) is a front-end for iptables that ships with Ubuntu. It gives you a readable interface for managing packet filtering without writing raw iptables rules. This guide covers practical hardening: default deny, SSH protection, web server rules, and logging.
Prerequisites
- Ubuntu 20.04 or later
- Root or sudo access
- An active SSH session (read the SSH section before enabling UFW to avoid locking yourself out)
Check UFW Status
sudo ufw status verbose
# If inactive, no rules are applied yet
Step 1: Set Default Policies
The most important step: deny all incoming traffic and allow all outgoing by default.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny forward
Do not enable UFW yet — add SSH rules first.
Step 2: Allow SSH Before Enabling
If you lock yourself out of SSH on a remote server, you will need console access to recover.
# Allow SSH on default port 22
sudo ufw allow ssh
# Or specify explicitly
sudo ufw allow 22/tcp
# If you run SSH on a custom port (e.g., 2222)
sudo ufw allow 2222/tcp
For extra protection, limit SSH to a specific source IP:
# Only allow SSH from your office IP
sudo ufw allow from 203.0.113.50 to any port 22 proto tcp
Step 3: Enable UFW
sudo ufw enable
# Command may disrupt existing connections. Proceed with operation (y|n)? y
sudo ufw status verbose
Output should show:
Status: active
Default: deny (incoming), allow (outgoing), deny (routed)
Step 4: Common Service Rules
Web Server
# HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Or use the application profile
sudo ufw allow 'Nginx Full'
sudo ufw allow 'Apache Full'
DNS (if running a resolver)
sudo ufw allow 53/tcp
sudo ufw allow 53/udp
Mail Server
sudo ufw allow 25/tcp # SMTP
sudo ufw allow 587/tcp # Submission
sudo ufw allow 993/tcp # IMAPS
sudo ufw allow 465/tcp # SMTPS
PostgreSQL (only from app server)
sudo ufw allow from 10.0.0.5 to any port 5432 proto tcp
Step 5: Rate Limit SSH
UFW has a built-in rate limit that blocks an IP after 6 connection attempts within 30 seconds.
# Replace the plain allow rule with a rate-limited one
sudo ufw delete allow 22/tcp
sudo ufw limit ssh
# Verify
sudo ufw status numbered
This mitigates brute-force attacks without needing fail2ban for basic SSH protection.
Step 6: Block Known Bad Ranges
Block entire CIDR ranges when you want to deny traffic from a region or known malicious ASN:
# Block a range
sudo ufw deny from 192.0.2.0/24
# Block and log
sudo ufw deny log from 198.51.100.0/24
Note: UFW processes rules in order. A deny before a more specific allow will block that traffic. List rules with numbers to check ordering:
sudo ufw status numbered
Delete a rule by number:
sudo ufw delete 3
Step 7: Application Profiles
UFW ships with application profiles in /etc/ufw/applications.d/. List available profiles:
sudo ufw app list
sudo ufw app info 'Nginx Full'
Create a custom profile for your application:
sudo tee /etc/ufw/applications.d/myapp <<EOF
[MyApp]
title=My Application
description=Custom app on port 8080
ports=8080/tcp
EOF
sudo ufw app update MyApp
sudo ufw allow MyApp
Step 8: Enable Logging
# Low logging: blocked packets only
sudo ufw logging on
# Medium: includes new connections
sudo ufw logging medium
# High: all packets (very verbose)
sudo ufw logging high
Logs appear in /var/log/ufw.log and also via syslog/journalctl:
sudo tail -f /var/log/ufw.log
sudo journalctl -k --grep="UFW" -f
Sample blocked packet log entry:
[UFW BLOCK] IN=eth0 OUT= MAC=... SRC=203.0.113.99 DST=10.0.0.1 \
LEN=44 TTL=244 ID=54321 DF PROTO=TCP SPT=44231 DPT=3306 FLAGS=S
Step 9: IPv6 Support
Ensure IPv6 rules are also applied. Open /etc/default/ufw and confirm:
sudo grep IPV6 /etc/default/ufw
# Should show: IPV6=yes
If it is no, change it and reload:
sudo ufw disable && sudo ufw enable
UFW will then apply matching rules to both IPv4 and IPv6 interfaces automatically.
Step 10: Allow Docker Containers
Docker bypasses UFW by default, writing directly to iptables. To fix this without patching Docker:
Edit /etc/ufw/after.rules and add before COMMIT:
# Allow forwarding for Docker
-A ufw-before-forward -i docker0 -j ACCEPT
-A ufw-before-forward -o docker0 -m state --state RELATED,ESTABLISHED -j ACCEPT
A cleaner solution is to bind Docker to 127.0.0.1 in /etc/docker/daemon.json:
{
"iptables": false
}
Then manage all exposure through UFW explicitly.
Useful Management Commands
# Show rules with line numbers
sudo ufw status numbered
# Reset all rules (dangerous — disables UFW)
sudo ufw reset
# Reload after manual edits
sudo ufw reload
# Delete a rule by specification
sudo ufw delete allow 8080/tcp
# Check before enabling (dry run)
sudo ufw --dry-run enable
Related Reading
- How to Detect Keyloggers on Your System
- Securing Docker Containers Best Practices
- How to Set Up Nebula Mesh VPN for Teams
- How to Set Up AppArmor Profiles on Ubuntu
- AI Coding Assistant Session Data Lifecycle
- How to Audit What Source Code AI Coding Tools Transmit