UFW (Uncomplicated Firewall) is the default firewall management tool on Ubuntu and other Debian-based distributions. It provides a straightforward command-line interface on top of iptables (or nftables on newer kernels) for managing host-based firewall rules. Instead of writing raw iptables rules, UFW lets you allow or block traffic with simple, readable commands.
This guide covers every common UFW operation – from basic allow/deny rules to rate limiting, Docker integration, port forwarding, and logging. All commands were tested on Ubuntu 24.04 with UFW 0.36.2.
Prerequisites
To follow this guide, you need:
- Ubuntu 24.04 or 22.04 (server or desktop)
- Root access or a user with sudo privileges
- SSH access to the server (if remote)
Step 1: Install and Enable UFW
UFW comes pre-installed on Ubuntu. Verify it is present and check the version:
sudo apt install ufw
Check the installed version:
ufw version
Output:
ufw 0.36.2
Copyright 2008-2023 Canonical Ltd.
Before enabling UFW, set default policies. This is important because the defaults determine what happens to traffic that does not match any explicit rule. Deny all incoming connections and allow all outgoing:
sudo ufw default deny incoming
sudo ufw default allow outgoing
If you are connected over SSH, allow SSH traffic before enabling UFW. Skipping this step will lock you out of the server:
sudo ufw allow ssh
Now enable UFW:
sudo ufw enable
Output:
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Verify the firewall status with verbose output:
sudo ufw status verbose
Output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
Step 2: Allow Services and Ports
UFW accepts both service names (from /etc/services) and port numbers. Here are the most common ways to open ports.
Allow by service name:
sudo ufw allow http
sudo ufw allow https
Allow by port number and protocol:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Allow a port with a comment (useful for tracking why a rule exists):
sudo ufw allow 8080/tcp comment 'Web app'
Allow a range of ports. You must specify the protocol when using ranges:
sudo ufw allow 6000:6100/tcp
Allow UDP traffic on a specific port (for example, DNS):
sudo ufw allow 53/udp
Step 3: Allow from Specific IPs and Subnets
For tighter security, restrict access to specific source IPs or subnets instead of opening ports to the entire internet.
Allow all traffic from a single IP address:
sudo ufw allow from 10.0.1.50
Allow all traffic from a subnet:
sudo ufw allow from 10.0.1.0/24
Allow a subnet to connect to a specific port (for example, MySQL on port 3306):
sudo ufw allow from 192.168.1.0/24 to any port 3306
Allow an IP to reach a specific port on a specific network interface:
sudo ufw allow in on eth0 from 10.0.1.0/24 to any port 22
This is useful on multi-homed servers where you want SSH access only on the management interface.
Step 4: Deny and Reject Traffic
UFW supports two ways to block traffic: deny and reject. Both prevent the connection, but they behave differently:
- deny – silently drops the packet. The sender gets no response and eventually times out.
- reject – drops the packet but sends an ICMP “port unreachable” response. The sender immediately knows the connection was refused.
Use deny for external-facing rules (attackers learn nothing) and reject for internal networks (faster feedback for legitimate users hitting the wrong port).
Deny all traffic from a specific IP:
sudo ufw deny from 203.0.113.100
Deny an entire subnet:
sudo ufw deny from 203.0.113.0/24
Deny outgoing traffic on a port (for example, block outbound SMTP to prevent spam):
sudo ufw deny out 25
Reject incoming traffic on a specific interface and port:
sudo ufw reject in on eth0 to any port 80
Step 5: Rate Limiting for Brute Force Protection
UFW has a built-in rate limiting feature that denies connections from an IP address if it attempts 6 or more connections within 30 seconds. This is effective against SSH brute force attacks without needing additional tools like Fail2ban.
Enable rate limiting on SSH:
sudo ufw limit ssh/tcp
Output:
Rule updated
Rule updated (v6)
If you previously had a plain allow rule for SSH, the limit command replaces it. You can verify with sudo ufw status – the Action column will show LIMIT IN instead of ALLOW IN.
For production servers exposed to the internet, rate limiting SSH is one of the first hardening steps you should apply. For more advanced blocking patterns (repeated failures across longer time windows, multiple services), install Fail2ban alongside UFW.
Step 6: Delete UFW Rules
There are two ways to delete rules: by rule number or by rule specification.
First, list all rules with their numbers:
sudo ufw status numbered
Output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp LIMIT IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 8080/tcp ALLOW IN Anywhere # Web app
[ 5] 6000:6100/tcp ALLOW IN Anywhere
[ 6] 53/udp ALLOW IN Anywhere
[ 7] Anywhere ALLOW IN 10.0.1.50
[ 8] Anywhere ALLOW IN 10.0.1.0/24
[ 9] 3306 ALLOW IN 192.168.1.0/24
[10] 22/tcp (v6) LIMIT IN Anywhere (v6)
[11] 80/tcp (v6) ALLOW IN Anywhere (v6)
[12] 443/tcp (v6) ALLOW IN Anywhere (v6)
[13] 8080/tcp (v6) ALLOW IN Anywhere (v6)
[14] 6000:6100/tcp (v6) ALLOW IN Anywhere (v6)
[15] 53/udp (v6) ALLOW IN Anywhere (v6)
Delete a rule by its number. For example, to remove the port range rule at position 5:
sudo ufw delete 5
Delete a rule by specification (matches the exact rule you originally added):
sudo ufw delete allow 6000:6100/tcp
When deleting by number, rule numbers shift after each deletion. If you need to delete multiple rules, delete from the highest number first to avoid renumbering issues.
Step 7: Insert Rules at a Specific Position
UFW evaluates rules in order – the first matching rule wins. This means rule position matters. If a deny rule appears before an allow rule for the same traffic, the deny takes precedence.
Insert a rule at a specific position using insert:
sudo ufw insert 1 allow from 10.0.1.50
This places the rule at position 1 (top of the list), ensuring it is evaluated before any other rules. This is useful when you have a general deny rule but want to add an exception for a trusted IP.
Step 8: Application Profiles
Application profiles are predefined rule sets stored in /etc/ufw/applications.d/. Packages like OpenSSH and Nginx install their own profiles automatically.
List available application profiles:
sudo ufw app list
Output (on a minimal Ubuntu install):
Available applications:
OpenSSH
View details about a specific profile:
sudo ufw app info OpenSSH
Output:
Profile: OpenSSH
Title: Secure Shell Server
Description: OpenSSH is a free implementation of the Secure Shell protocol.
Ports:
22/tcp
Allow traffic using the application profile name:
sudo ufw allow OpenSSH
Create Custom Application Profiles
You can create custom profiles for your own applications. Create a file in /etc/ufw/applications.d/:
sudo nano /etc/ufw/applications.d/mywebapp
Add the following content:
[MyWebApp]
title=My Custom Web Application
description=Web application running on port 8443
ports=8443/tcp
After creating the profile, reload the app list and allow it:
sudo ufw app update MyWebApp
sudo ufw allow MyWebApp
Step 9: UFW Logging
UFW logging helps you monitor blocked connections, detect scanning attempts, and troubleshoot connectivity issues.
Enable logging:
sudo ufw logging on
UFW supports five logging levels, each progressively more verbose:
- off – no logging
- low – logs blocked packets that do not match the default policy, plus packets matching logged rules
- medium – same as low plus invalid packets, new connections, and rate-limited packets
- high – same as medium plus all packets with rate limiting
- full – logs everything without rate limiting (generates a lot of output)
Set the logging level:
sudo ufw logging medium
Output:
Logging enabled
UFW writes logs to /var/log/ufw.log. Each log entry contains useful information:
sudo tail -5 /var/log/ufw.log
A typical log line looks like:
Mar 23 10:15:42 server kernel: [UFW BLOCK] IN=eth0 OUT= MAC=... SRC=203.0.113.50 DST=10.0.1.10 LEN=44 TOS=0x00 PROTO=TCP SPT=54321 DPT=22 WINDOW=1024 SYN
Key fields to look at:
- [UFW BLOCK] – the action taken (BLOCK, ALLOW, or AUDIT)
- SRC – source IP address
- DST – destination IP address
- DPT – destination port
- PROTO – protocol (TCP, UDP, ICMP)
Step 10: UFW with Docker (Critical Fix)
This is one of the most common pitfalls for Ubuntu servers running Docker. Docker manipulates iptables directly by inserting rules into the DOCKER chain, which bypasses UFW entirely. This means a container exposing port 8080 is accessible from the internet even if UFW has no rule allowing port 8080.
Fix 1: Use the DOCKER-USER Chain
Docker provides a DOCKER-USER chain specifically for user-defined rules. Add custom iptables rules to /etc/ufw/after.rules (before the COMMIT line at the end of the file):
sudo nano /etc/ufw/after.rules
Add the following block at the end of the file:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-docker-logging-deny
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
Then restart UFW:
sudo ufw reload
With this configuration, Docker containers can communicate with each other and reach the internet, but external traffic to container ports is blocked unless you explicitly allow it through UFW.
Fix 2: Disable Docker’s iptables Management
An alternative approach is to disable Docker’s iptables manipulation entirely. Edit or create /etc/docker/daemon.json:
sudo nano /etc/docker/daemon.json
Add this configuration:
{
"iptables": false
}
Restart Docker:
sudo systemctl restart docker
With this approach, you manage all port exposure through UFW manually. Container-to-container networking still works over Docker networks, but published ports will not be reachable from external hosts unless you add UFW rules for them. This gives you full control but requires more manual configuration.
Step 11: Port Forwarding and NAT with UFW
UFW can handle port forwarding (DNAT) by adding rules to /etc/ufw/before.rules. This is useful when your server acts as a gateway and you need to forward incoming traffic to an internal server.
First, enable IP forwarding. Edit /etc/ufw/sysctl.conf:
sudo nano /etc/ufw/sysctl.conf
Uncomment or add this line:
net/ipv4/ip_forward=1
Next, add NAT rules to /etc/ufw/before.rules. Add the following block before the *filter section at the top of the file:
sudo nano /etc/ufw/before.rules
Add before the *filter line:
# NAT table rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Forward port 80 to internal server 10.0.1.20
-A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.1.20:80
# Masquerade outgoing traffic from internal network
-A POSTROUTING -s 10.0.1.0/24 -o eth0 -j MASQUERADE
COMMIT
Also allow forwarded traffic in UFW. Change the default forward policy in /etc/default/ufw:
sudo nano /etc/default/ufw
Change this line:
DEFAULT_FORWARD_POLICY="ACCEPT"
Reload UFW to apply the changes:
sudo ufw reload
Step 12: Reset and Disable UFW
To disable UFW without removing any rules (rules are preserved and will be active again when you re-enable):
sudo ufw disable
Output:
Firewall stopped and disabled on system startup
To completely reset UFW, removing all rules and returning to default settings:
sudo ufw reset
Output:
Resetting all rules to installed defaults. This may disrupt existing ssh
connections. Proceed with operation (y|n)? y
Backing up 'user.rules' to '/etc/ufw/user.rules.20260323_101542'
Backing up 'before.rules' to '/etc/ufw/before.rules.20260323_101542'
Backing up 'after.rules' to '/etc/ufw/after.rules.20260323_101542'
Backing up 'user6.rules' to '/etc/ufw/user6.rules.20260323_101542'
Backing up 'before6.rules' to '/etc/ufw/before6.rules.20260323_101542'
Backing up 'after6.rules' to '/etc/ufw/after6.rules.20260323_101542'
UFW backs up your rules before resetting, so you can restore them if needed. After a reset, you need to re-enable UFW and add your rules from scratch.
Step 13: UFW vs iptables vs nftables vs firewalld
Linux has several firewall management tools. Here is how they compare:
| Feature | UFW | iptables | nftables | firewalld |
|---|---|---|---|---|
| Ease of use | Very easy | Complex | Moderate | Easy |
| Default on | Ubuntu, Debian | Legacy Linux | Debian 11+, RHEL 9+ | RHEL, Rocky, Alma, Fedora |
| Backend | iptables or nftables | Netfilter (legacy) | Netfilter (modern) | nftables (or iptables) |
| Zone support | No | No | No | Yes |
| Dynamic reload | No (requires reload) | No | Yes | Yes |
| IPv6 support | Yes (automatic) | Separate ip6tables | Yes (unified) | Yes (automatic) |
| Rich rules | Limited | Full control | Full control | Rich rule syntax |
| Best for | Single servers, VPS | Advanced custom rules | Modern custom rules | Enterprise RHEL servers |
For Ubuntu servers, UFW is the right choice for most use cases. If you need zone-based rules or manage RHEL-family systems, see our guide on configuring firewalld on Rocky Linux / AlmaLinux / RHEL. For Ubuntu systems where you prefer firewalld over UFW, we also have a guide on installing firewalld on Ubuntu.
Quick Reference: Common UFW Commands
The table below summarizes every common UFW command covered in this guide for quick reference.
| Action | Command |
|---|---|
| Enable UFW | sudo ufw enable |
| Disable UFW | sudo ufw disable |
| Check status | sudo ufw status verbose |
| Set default deny incoming | sudo ufw default deny incoming |
| Set default allow outgoing | sudo ufw default allow outgoing |
| Allow port (TCP) | sudo ufw allow 80/tcp |
| Allow port (UDP) | sudo ufw allow 53/udp |
| Allow service by name | sudo ufw allow ssh |
| Allow port range | sudo ufw allow 6000:6100/tcp |
| Allow from IP | sudo ufw allow from 10.0.1.50 |
| Allow from subnet to port | sudo ufw allow from 192.168.1.0/24 to any port 3306 |
| Deny from IP | sudo ufw deny from 203.0.113.100 |
| Deny outgoing port | sudo ufw deny out 25 |
| Reject traffic | sudo ufw reject in on eth0 to any port 80 |
| Rate limit SSH | sudo ufw limit ssh/tcp |
| Delete rule by number | sudo ufw delete 5 |
| Delete rule by spec | sudo ufw delete allow 80/tcp |
| Insert rule at position | sudo ufw insert 1 allow from 10.0.1.50 |
| List app profiles | sudo ufw app list |
| Show numbered rules | sudo ufw status numbered |
| Enable logging | sudo ufw logging medium |
| Reset all rules | sudo ufw reset |
| Reload rules | sudo ufw reload |
Conclusion
UFW provides a straightforward way to manage firewall rules on Ubuntu servers. For production deployments, keep these practices in mind:
- Always set default policies (deny incoming, allow outgoing) before adding specific rules
- Allow SSH before enabling UFW on remote servers – getting locked out is painful
- Use rate limiting on SSH (
sudo ufw limit ssh/tcp) as a first line of defense against brute force - If running Docker, apply the DOCKER-USER chain fix – Docker bypasses UFW by default
- Restrict database ports (3306, 5432, 6379) to specific IPs or subnets, never open them to the world
- Enable logging at
mediumlevel for good visibility without excessive noise - Review rules periodically with
sudo ufw status numberedand remove anything no longer needed
For securing SSH access on your Ubuntu server beyond firewall rules, see our guide on installing and configuring SSH server on Ubuntu. If you run a WireGuard VPN, remember to allow the VPN port (typically 51820/udp) in UFW as well.