ufw — Uncomplicated Firewall Reference

Uncomplicated Firewall — a human-friendly front-end for iptables and nftables, designed to make host-based firewalling on Ubuntu and Debian approachable without sacrificing capability.

allow · deny · limit · app profiles · NAT · logging · IPv4 · IPv6 · Ubuntu · Debian
01 — What is ufw?

ufw is the default firewall tool on Ubuntu. It wraps iptables (and nftables on newer systems) in a straightforward command interface, eliminating the need to understand chain syntax for common tasks. Rules are expressed as plain English: allow ssh, deny 3306, limit 22/tcp.

ufw makes the 90% case — open a port, close a port, rate-limit SSH — a single readable command rather than an iptables essay.

For anything ufw can't express directly — complex NAT, per-interface rules, advanced conntrack — its underlying rule files (/etc/ufw/before.rules, after.rules) accept raw iptables syntax that is merged in automatically.

ConceptMeaning
RuleAn allow, deny, reject, or limit statement applied to a port, service, IP, or app profile.
App profileA named set of ports defined in /etc/ufw/applications.d/. Similar to firewalld services.
Default policyWhat happens to traffic that matches no rule: allow, deny, or reject.
before.rules / after.rulesRaw iptables rules merged before and after ufw's own rules. Used for NAT and advanced config.
Numbered rulesEvery rule has an index. Used to insert or delete rules at specific positions.
02 — Installation & First Enable
Debian / Ubuntu
# Pre-installed on Ubuntu. If missing: sudo apt update && sudo apt install ufw
Arch Linux
sudo pacman -S ufw sudo systemctl enable --now ufw
First-time setup — safe sequence
# 1. Set default policies BEFORE enabling — deny all inbound, allow all outbound sudo ufw default deny incoming sudo ufw default allow outgoing # 2. Allow SSH BEFORE enabling — otherwise you lock yourself out sudo ufw allow ssh # 3. Enable the firewall sudo ufw enable # 4. Check status sudo ufw status verbose
⚠  Always allow SSH before running ufw enable on a remote server. The default incoming deny policy will block your own connection the instant the firewall activates.
Enable / disable / reset
# Enable (persists across reboots) sudo ufw enable # Disable completely (rules preserved but not applied) sudo ufw disable # Reset — wipe all rules and start fresh sudo ufw reset # Check status sudo ufw status sudo ufw status verbose # shows default policies and all rule details sudo ufw status numbered # shows rules with index numbers
03 — Default Policies

Default policies determine what happens to packets that match no rule. The recommended starting point is deny incoming, allow outgoing — then selectively open inbound services.

# Standard server defaults sudo ufw default deny incoming sudo ufw default allow outgoing # Stricter — also restrict outbound (adds rules as you go) sudo ufw default deny outgoing # Reject instead of drop (sends ICMP error to sender) sudo ufw default reject incoming # Forward policy — relevant if this host routes traffic sudo ufw default deny routed
PolicyEffect on unmatched packets
denySilently drop — sender gets no response (stealth).
rejectDrop and send ICMP "port unreachable" — sender knows it was blocked.
allowPass through — use for outgoing or in fully trusted environments.
04 — Allowing & Denying Traffic

Rules can be specified by service name, port number, protocol, IP address, or any combination. ufw resolves service names from /etc/services.

By service name
# Allow SSH (resolves to TCP 22) sudo ufw allow ssh # Allow HTTP and HTTPS sudo ufw allow http sudo ufw allow https # Deny FTP sudo ufw deny ftp
By port number
# Allow a specific TCP port sudo ufw allow 8080/tcp # Allow a specific UDP port sudo ufw allow 51820/udp # Allow a port range sudo ufw allow 60000:61000/udp # Allow both TCP and UDP on a port (omit protocol) sudo ufw allow 53
By source IP
# Allow all traffic from a specific IP sudo ufw allow from 203.0.113.10 # Allow all traffic from a subnet sudo ufw allow from 10.0.0.0/8 # Allow a specific IP to reach a specific port sudo ufw allow from 203.0.113.10 to any port 22 # Allow a subnet to reach a specific port and protocol sudo ufw allow from 10.0.0.0/24 to any port 5432 proto tcp # Deny a specific IP sudo ufw deny from 198.51.100.99 # Deny a subnet sudo ufw deny from 198.51.100.0/24
By destination interface
# Allow HTTP only on a specific network interface sudo ufw allow in on eth0 to any port 80 # Allow traffic on the loopback interface sudo ufw allow in on lo # Deny traffic arriving on eth1 sudo ufw deny in on eth1 to any port 3306
Reject (sends ICMP error instead of silent drop)
sudo ufw reject 23/tcp # Telnet — reject with a message sudo ufw reject smtp
05 — The limit Rule (Rate Limiting)

The limit rule is ufw's built-in brute-force mitigation. It allows connections but blocks source IPs that make more than 6 connection attempts within 30 seconds — no additional tools required.

# Rate-limit SSH (the most common use case) sudo ufw limit ssh # Rate-limit by port number sudo ufw limit 22/tcp # Rate-limit a custom port sudo ufw limit 2222/tcp # Rate-limit a web application login endpoint (any TCP port) sudo ufw limit 8080/tcp
ℹ  The limit threshold is fixed at 6 connections in 30 seconds — it cannot be tuned through ufw. For finer-grained rate limiting (per-IP thresholds, longer windows), use fail2ban alongside ufw or drop down to raw iptables hashlimit rules in /etc/ufw/before.rules.
06 — Application Profiles

Application profiles are named port groups defined in /etc/ufw/applications.d/ — the ufw equivalent of firewalld services. Many packages install their own profiles automatically.

Listing and using profiles
# List all available profiles sudo ufw app list # Show what ports a profile includes sudo ufw app info 'Nginx Full' sudo ufw app info OpenSSH # Allow by profile name sudo ufw allow 'Nginx Full' # HTTP + HTTPS sudo ufw allow 'Nginx HTTP' # HTTP only sudo ufw allow 'Nginx HTTPS' # HTTPS only sudo ufw allow OpenSSH # Deny a profile sudo ufw deny 'Apache Full'
Common built-in profiles
Profile namePorts
OpenSSHTCP 22
Nginx FullTCP 80, 443
Nginx HTTPTCP 80
Nginx HTTPSTCP 443
Apache FullTCP 80, 443
ApacheTCP 80
Apache SecureTCP 443
PostfixTCP 25, 587
Postfix SMTPSTCP 465
Dovecot IMAPTCP 143, 993
Dovecot POP3TCP 110, 995
SambaTCP 139, 445 · UDP 137, 138
Creating a custom profile
# /etc/ufw/applications.d/myapp [MyApp] title=My Application description=Custom web application API and admin interface ports=8080,8443/tcp
# Reload profiles and use it sudo ufw app update MyApp sudo ufw allow MyApp
07 — Managing Rules

Rules are evaluated in order — the first matching rule wins. Managing order matters when allow and deny rules could conflict.

Viewing rules
# Simple view sudo ufw status # Verbose — shows policies and direction sudo ufw status verbose # Numbered — shows index for each rule sudo ufw status numbered
Deleting rules
# Delete by rule specification (exact match) sudo ufw delete allow ssh sudo ufw delete allow 8080/tcp sudo ufw delete deny from 198.51.100.99 # Delete by rule number (get numbers from: sudo ufw status numbered) sudo ufw delete 3 # Delete both IPv4 and IPv6 versions of a rule sudo ufw delete allow 80/tcp
Inserting rules at a specific position
# Insert a DENY rule at position 1 (top — evaluated first) sudo ufw insert 1 deny from 198.51.100.99 # Insert an ALLOW rule at position 2 sudo ufw insert 2 allow from 10.0.0.5 to any port 22
IPv6 rules
# ufw manages IPv4 and IPv6 simultaneously by default # Confirm IPv6 is enabled in /etc/default/ufw: grep IPV6 /etc/default/ufw # Should show: IPV6=yes # Rules specified without a family apply to both IPv4 and IPv6 sudo ufw allow https # opens TCP 443 for both # Family-specific rules sudo ufw allow proto tcp from 2001:db8::/32 to any port 22
ℹ  If IPV6=yes is set in /etc/default/ufw (the default), every rule you add automatically creates both an IPv4 and an IPv6 version. You can see both when running ufw status numbered.
08 — Common Service Configurations
SSH
# Allow SSH from anywhere (with rate limiting — recommended) sudo ufw limit ssh # Allow SSH from a specific IP or subnet only sudo ufw allow from 203.0.113.0/24 to any port 22 # Custom SSH port sudo ufw allow 2222/tcp sudo ufw limit 2222/tcp
Web server
# Nginx — HTTP and HTTPS sudo ufw allow 'Nginx Full' # Apache — HTTP and HTTPS sudo ufw allow 'Apache Full' # Manual — if no profile exists sudo ufw allow 80/tcp sudo ufw allow 443/tcp
Mail server
sudo ufw allow 25/tcp # SMTP sudo ufw allow 587/tcp # Submission sudo ufw allow 465/tcp # SMTPS sudo ufw allow 143/tcp # IMAP sudo ufw allow 993/tcp # IMAPS sudo ufw allow 110/tcp # POP3 sudo ufw allow 995/tcp # POP3S
FTP
sudo ufw allow 21/tcp # FTP control sudo ufw allow 20/tcp # FTP active data sudo ufw allow 60000:61000/tcp # Passive FTP data range # Also configure your FTP daemon to use this passive port range
DNS server
sudo ufw allow 53/tcp sudo ufw allow 53/udp
Databases — restricted to internal network
# MySQL — app servers only sudo ufw allow from 10.0.0.0/24 to any port 3306 # PostgreSQL — specific host only sudo ufw allow from 10.0.0.20 to any port 5432 # Redis — localhost only (typically no firewall rule needed) sudo ufw deny 6379 # belt-and-suspenders block
VPN
# WireGuard sudo ufw allow 51820/udp # OpenVPN sudo ufw allow 1194/udp
Monitoring & internal tools
# Prometheus node exporter — internal only sudo ufw allow from 10.0.0.0/8 to any port 9100 # Grafana — internal only sudo ufw allow from 10.0.0.0/8 to any port 3000
09 — NAT & IP Forwarding

ufw doesn't expose NAT through its CLI — but it fully supports masquerade and DNAT through its underlying rule files. Edit /etc/ufw/before.rules and /etc/default/ufw directly.

Step 1 — enable IP forwarding
# /etc/default/ufw — change the forward policy DEFAULT_FORWARD_POLICY="ACCEPT"
# /etc/ufw/sysctl.conf — uncomment these lines net/ipv4/ip_forward=1 net/ipv6/conf/default/forwarding=1 net/ipv6/conf/all/forwarding=1
Step 2 — add NAT rules to before.rules
# /etc/ufw/before.rules # Add this block at the very TOP of the file, before the *filter section *nat :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] # Masquerade — all traffic leaving eth0 gets the host's public IP -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE # DNAT — forward external TCP:80 to an internal web server -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.10:80 COMMIT # The existing *filter section continues below... *filter ...
Step 3 — allow forwarded traffic
# Allow forwarding from LAN to WAN sudo ufw allow in on eth1 out on eth0 sudo ufw allow in on eth1 out on eth0 from 192.168.1.0/24 # Reload to apply sudo ufw disable && sudo ufw enable
ℹ  Changes to before.rules take effect on the next ufw disable && ufw enable cycle or system reboot. A simple ufw reload does not re-read the raw rule files.
10 — Logging

ufw can log dropped and allowed packets at several verbosity levels. Logs go to /var/log/ufw.log and are also visible in the system journal.

Log levels
# Disable logging sudo ufw logging off # Log blocked packets only (default — recommended for most servers) sudo ufw logging on # Log all packets matching a rule that has logging enabled sudo ufw logging low # blocked packets sudo ufw logging medium # blocked + some allowed sudo ufw logging high # all packets — very verbose sudo ufw logging full # maximum detail
Per-rule logging
# Log SSH connection attempts explicitly sudo ufw allow log 22/tcp # Log and rate-limit SSH sudo ufw limit log ssh # Log denied traffic from a specific subnet sudo ufw deny log from 198.51.100.0/24
Reading the log
# Follow the ufw log live sudo tail -f /var/log/ufw.log # Via journald sudo journalctl -k | grep UFW # Filter for blocked packets only sudo grep "UFW BLOCK" /var/log/ufw.log # Filter for allowed traffic sudo grep "UFW ALLOW" /var/log/ufw.log # Count blocked IPs — top offenders sudo grep "UFW BLOCK" /var/log/ufw.log \ | grep -oP 'SRC=\S+' | sort | uniq -c | sort -rn | head -20
Log entry anatomy
# Example log line: Mar 17 10:23:01 host kernel: [UFW BLOCK] IN=eth0 OUT= MAC=... \ SRC=198.51.100.99 DST=10.0.0.1 LEN=60 TOS=0x00 PREC=0x00 \ TTL=49 ID=12345 DF PROTO=TCP SPT=54321 DPT=22 WINDOW=64240 \ RES=0x00 SYN URGP=0
FieldMeaning
UFW BLOCK / ALLOWWhether the packet was dropped or passed.
IN= / OUT=Incoming and outgoing interface names.
SRC= / DST=Source and destination IP addresses.
PROTO=Protocol: TCP, UDP, ICMP.
SPT= / DPT=Source port and destination port.
SYNTCP SYN flag set — a new connection attempt.
11 — /etc/default/ufw & Advanced Config

The /etc/default/ufw file controls global ufw behaviour — the knobs that the CLI doesn't expose.

/etc/default/ufw — key settings
# /etc/default/ufw # Enable IPv6 rules (strongly recommended — leave as yes) IPV6=yes # Default policies (also settable via CLI, but this is the persistent source) DEFAULT_INPUT_POLICY="DROP" DEFAULT_OUTPUT_POLICY="ACCEPT" DEFAULT_FORWARD_POLICY="DROP" # Set to ACCEPT for routing/NAT # Logging level LOGLEVEL="low" # Manage built-in chains — leave as yes unless you know why you'd change it MANAGE_BUILTINS=no
Raw rule files
FilePurpose
/etc/ufw/before.rulesRaw iptables rules added before ufw's own rules. Used for NAT (PREROUTING / POSTROUTING) and early custom rules.
/etc/ufw/after.rulesRaw iptables rules added after ufw's own rules. Used for LOGGING rules and catch-all overrides.
/etc/ufw/before6.rulesIPv6 equivalent of before.rules.
/etc/ufw/after6.rulesIPv6 equivalent of after.rules.
/etc/ufw/user.rulesufw's own generated rules — do not edit manually.
/etc/ufw/applications.d/App profile definitions.
/etc/ufw/sysctl.confKernel parameters applied when ufw starts (forwarding, rp_filter, etc.).
12 — Docker & ufw

Docker bypasses ufw by writing its own iptables rules directly — published ports (-p) become accessible from the internet even if ufw has no allow rule for them. This is a well-known and long-standing footgun.

The problem
# ufw blocks port 8080: sudo ufw deny 8080 # But Docker publishes it anyway — 8080 is publicly reachable: docker run -p 8080:80 nginx # ← bypasses ufw entirely
Fix A — disable Docker's iptables management (simplest, some caveats)
# /etc/docker/daemon.json { "iptables": false } # Then restart Docker sudo systemctl restart docker # Now manage container networking manually through ufw
Fix B — restrict published ports to localhost only
# Bind to 127.0.0.1 — only local processes can reach the container docker run -p 127.0.0.1:8080:80 nginx # Then use a reverse proxy (nginx, Caddy) on the host to expose selectively # ufw controls access to the reverse proxy port (80/443) normally
Fix C — ufw-docker (third-party tool)
# Install ufw-docker — patches the DOCKER-USER chain to respect ufw rules sudo wget -O /usr/local/bin/ufw-docker \ https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker sudo chmod +x /usr/local/bin/ufw-docker sudo ufw-docker install sudo systemctl restart ufw
⚠  Fix B (bind to 127.0.0.1) is the most reliable approach for production. Fix A can break container-to-container networking. Fix C works well but adds a third-party dependency.
13 — Troubleshooting
SymptomCause & Fix
Locked out after enableSSH rule not added before ufw enable. Use out-of-band console, run ufw allow ssh, reconnect.
Rule added but traffic still blockedAn earlier deny rule is matching first. Check order with ufw status numbered and insert an allow at a lower number.
Rule added but traffic still allowedA deny rule needs to come before the allow. Use ufw insert 1 deny ....
IPv6 not being filteredIPV6=yes missing or not set in /etc/default/ufw. Run ufw disable && ufw enable after fixing.
Docker ports exposed despite deny rulesDocker bypasses ufw — see section 12.
NAT rules not taking effectChanges to before.rules require ufw disable && ufw enableufw reload is not enough.
ufw status shows inactiveRun sudo ufw enable. After reboot, check sudo systemctl status ufw.
App profile not foundRun sudo ufw app update all to refresh the profile list after installing a new package.
Deleting a rule doesn't workThe rule specification must match exactly. Use ufw status numbered and delete by number instead.
Diagnostic commands
# Show full status with policies sudo ufw status verbose # Show rules with index numbers sudo ufw status numbered # See the actual iptables rules ufw generated sudo iptables -L -v -n --line-numbers sudo ip6tables -L -v -n --line-numbers # Check ufw service status sudo systemctl status ufw # Follow the ufw log sudo tail -f /var/log/ufw.log sudo journalctl -k -f | grep UFW # Dry-run check — what would happen without applying changes? # (ufw has no dry-run, so review numbered rules before deleting) sudo ufw status numbered
14 — Quick Reference
CommandWhat it does
ufw enableActivate the firewall (persists across reboots)
ufw disableDeactivate without deleting rules
ufw resetWipe all rules and return to defaults
ufw status verboseShow all rules and default policies
ufw status numberedShow rules with index numbers
ufw default deny incomingDrop all inbound traffic by default
ufw allow sshAllow inbound SSH (TCP 22)
ufw limit sshAllow SSH with rate limiting (6 conn / 30s)
ufw allow 8080/tcpOpen TCP port 8080
ufw allow from 10.0.0.0/8 to any port 22Allow SSH from a subnet only
ufw deny from 198.51.100.99Block all traffic from an IP
ufw insert 1 deny from <IP>Insert a deny rule at the top
ufw delete allow 8080/tcpDelete a rule by specification
ufw delete 3Delete rule number 3
ufw allow 'Nginx Full'Allow by app profile name
ufw app listList all available app profiles
ufw logging onEnable logging of blocked packets
✔  The safe sequence for any new server: ufw default deny incomingufw default allow outgoingufw limit ssh → open whatever your service needs → ufw enable. In that order, every time.