UFW firewall setup for Ubuntu: Default policies, allow and deny rules, rate limiting, application profiles, logging configuration, and advanced firewall techniques.

UFW firewall setup is one of the first things you do on a new Ubuntu server — and one of the easiest to get wrong. UFW (Uncomplicated Firewall) is the default firewall management tool on Ubuntu, sitting on top of iptables and translating human-readable commands into complex packet filtering rules. This guide covers everything from basic installation and default policies to rate limiting, application profiles, IP-based restrictions, logging, and the common pitfalls that lock people out of their own servers. Every command here works on Ubuntu 22.04+ and Debian 12+.

What UFW Is and Why It Replaces Raw iptables

Under the hood, Linux firewalls run on netfilter — a kernel-level packet filtering framework. The traditional interface is iptables, which is powerful but verbose. A single rule to allow SSH on port 22 requires specifying chain, protocol, port, and action in a syntax that's easy to get wrong:

# Raw iptables — don't do this manually
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

UFW wraps this into a single, readable command:

sudo ufw allow 22/tcp

Same result, far less room for error. UFW also handles rule persistence automatically — iptables rules are lost on reboot unless you save them separately. With UFW, rules survive reboots out of the box. I stopped writing raw iptables rules years ago because UFW covers every use case I encounter on single-server deployments. For complex multi-server setups with NAT and forwarding, raw iptables or nftables still has its place — but that's not what most people need.

Installing and Enabling UFW on Ubuntu

UFW comes pre-installed on most Ubuntu server images. On Debian or minimal installs, add it manually:

sudo apt update && sudo apt install -y ufw

Check the current status before making any changes:

sudo ufw status verbose

On a fresh install, this shows Status: inactive. Do not enable UFW yet — first set your default policies and allow SSH, or you will lose access.

What happens if I enable UFW without allowing SSH first?

You get locked out immediately. UFW's default-deny policy blocks all incoming connections the moment you run ufw enable. If SSH is not explicitly allowed before that, your current session may survive (existing connections are usually not killed), but any new SSH connection will be refused. If you close your terminal, you need your provider's recovery console or VNC access to fix it. I've done this exactly once — that was enough to make "allow SSH first" a reflex.

UFW Default Policies — The Foundation

Every firewall starts with default policies: what happens to traffic that doesn't match any explicit rule. The secure baseline is deny incoming, allow outgoing:

sudo ufw default deny incoming
sudo ufw default allow outgoing

deny incoming means every port is closed unless you explicitly open it. allow outgoing lets the server initiate connections — needed for package updates, DNS resolution, API calls, and time synchronization. Some hardening guides recommend deny outgoing as well, but in practice this breaks too many things (apt, curl, Docker image pulls) and the security benefit is marginal on a server you control.

Essential UFW Rules for Web Servers

With default-deny in place, open only the ports your services need. For a typical web server running SSH and a web application:

sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

The comment flag documents the purpose of each rule. When you run ufw status months later, you see why each port is open — not just that it is. Now enable the firewall:

sudo ufw enable

UFW asks for confirmation because it may disrupt existing connections. Type y to proceed. Verify:

sudo ufw status numbered

This shows all rules with index numbers, which you'll need later for deleting or inserting rules at specific positions.

How do I open a specific port with UFW?

The syntax follows a consistent pattern. You can specify protocol (tcp/udp), port ranges, and combine them:

# Single port, TCP only
sudo ufw allow 3000/tcp comment 'Node.js API'

# Single port, both TCP and UDP
sudo ufw allow 53 comment 'DNS'

# Port range
sudo ufw allow 8000:8100/tcp comment 'Dev servers'

# Deny a specific port explicitly
sudo ufw deny 3306/tcp comment 'Block MySQL from outside'

The deny rule is useful when you want to make an exception explicit — for example, ensuring MySQL is never accessible from the internet even if someone adds a broader allow rule later. Deny rules are evaluated before allow rules at the same priority level.

Rate Limiting and Brute-Force Protection with UFW

UFW includes a built-in rate limiter that blocks IPs exceeding 6 connection attempts within 30 seconds. This is ideal for SSH protection:

sudo ufw limit 22/tcp comment 'SSH rate limit'

This replaces the basic allow 22/tcp rule. Under the hood, UFW creates iptables rules using the recent module to track connection frequency per source IP. If an IP exceeds the threshold, new connections are dropped until the window expires.

Is UFW rate limiting enough or do I need Fail2Ban?

For SSH with key-only authentication, UFW rate limiting is often sufficient. It blocks the noisiest scanners and reduces log clutter. But UFW's rate limiter is simple — fixed threshold, fixed window, no ban duration. Fail2Ban offers more: configurable ban times, progressive bans for repeat offenders, support for any log-based service (SSH, Nginx, Postfix), and email notifications. On servers I manage, I use both: UFW limit as the first layer, and Fail2Ban on top for customizable banning. If you want to keep things minimal, UFW limit alone is a reasonable choice.

UFW Application Profiles

Many packages ship with UFW application profiles that define their port requirements. List available profiles:

sudo ufw app list

Common profiles include OpenSSH, Nginx Full, Nginx HTTP, Nginx HTTPS, and Apache Full. Use them instead of raw port numbers for clarity:

sudo ufw allow 'Nginx Full'
sudo ufw allow 'OpenSSH'

To see what ports a profile opens:

sudo ufw app info 'Nginx Full'

This outputs the title, description, and ports (80,443/tcp for Nginx Full). Application profiles live in /etc/ufw/applications.d/. You can create custom profiles for your own services — useful if you deploy the same stack across multiple servers and want consistent rule naming.

Allowing Traffic from Specific IP Addresses

Not every service should be open to the entire internet. Databases, admin panels, and monitoring endpoints should only accept connections from known IPs or internal networks.

How do I restrict SSH access to my IP only?

Replace the global SSH allow rule with an IP-specific one. First remove the old rule, then add the restricted one:

# Remove the global SSH rule (check the number first)
sudo ufw status numbered
sudo ufw delete 1

# Allow SSH only from your IP
sudo ufw allow from 203.0.113.50 to any port 22 proto tcp comment 'SSH from office'

# Allow an entire subnet (e.g. your VPN range)
sudo ufw allow from 10.8.0.0/24 to any port 22 proto tcp comment 'SSH from VPN'

This is the strongest SSH protection you can get at the firewall level — even if someone has your SSH key, they can't connect from an unauthorized IP. The downside: if your IP changes (home ISP, travel), you need your provider's console to update the rule. I use this on servers where I always connect through a VPN with a static IP. For servers I access from varying locations, I keep the global allow with rate limiting and Fail2Ban instead.

Restricting Database and Internal Service Access

Databases should never be open to the internet. If your application server and database run on the same host, the database listens on localhost only and no UFW rule is needed. If they're on separate servers:

# Allow PostgreSQL only from the application server
sudo ufw allow from 10.0.1.10 to any port 5432 proto tcp comment 'PostgreSQL from app server'

# Allow Redis only from the local network
sudo ufw allow from 10.0.1.0/24 to any port 6379 proto tcp comment 'Redis internal'

Verify with sudo ufw status that no global allow rule exists for these ports. A global allow 5432/tcp would override the IP restriction. For Docker-based deployments, use Docker Compose networks for inter-container communication instead of exposing ports to the host.

UFW Logging and Troubleshooting

Enable logging to see what UFW blocks and allows:

sudo ufw logging on
sudo ufw logging medium

Log levels: off, low, medium, high, full. For most servers, medium is the right balance — it logs blocked packets and new allowed connections without flooding the logs. Entries go to /var/log/ufw.log:

sudo tail -20 /var/log/ufw.log

Each line shows timestamp, source IP, destination port, protocol, and action (BLOCK or ALLOW). This is invaluable for debugging connectivity issues — if a service can't be reached, check the UFW log before assuming it's a service configuration problem.

How do I check which UFW rules are currently active?

Three views, each useful for different situations:

# Numbered list — for deleting/inserting rules
sudo ufw status numbered

# Verbose — shows default policies, logging level, and rule details
sudo ufw status verbose

# Raw iptables — see exactly what UFW generated
sudo iptables -L -n -v

If rules aren't behaving as expected, the raw iptables output shows the actual packet counts per rule — this tells you which rules are matching traffic and which are never hit.

Deleting and Modifying UFW Rules

UFW doesn't have an "edit" command. To change a rule, delete the old one and add a new one. Delete by rule number or by rule specification:

# Delete by number (get numbers with 'ufw status numbered')
sudo ufw delete 3

# Delete by specification (must match exactly)
sudo ufw delete allow 80/tcp

To insert a rule at a specific position (rules are evaluated top to bottom):

# Insert a deny rule at position 1 (before any allow rules)
sudo ufw insert 1 deny from 198.51.100.0/24 to any comment 'Block known scanner range'

Rule order matters. UFW evaluates rules from top to bottom and applies the first match. A broad allow rule above a narrow deny rule means the deny never triggers. When in doubt, put deny rules before allow rules.

Advanced UFW Configurations

Can I use UFW with Docker?

This is a common pitfall. Docker modifies iptables directly to publish container ports, and these rules bypass UFW entirely. If you run docker run -p 3306:3306 mysql, that port is accessible from the internet even if UFW has no rule for it — Docker inserts its own FORWARD chain rules that UFW doesn't manage.

The fix depends on your setup:

I use the first approach on every production server — only Nginx is exposed, everything else communicates over Docker's internal networks.

IPv6 Support

UFW supports IPv6 out of the box. Ensure it's enabled in /etc/default/ufw:

# /etc/default/ufw
IPV6=yes

When you add a rule like ufw allow 22/tcp, UFW creates both IPv4 and IPv6 rules automatically. If your server has an IPv6 address (most providers assign one), leaving IPv6 unprotected while hardening IPv4 is a common oversight — attackers can reach services through the IPv6 address that you thought were firewalled.

Resetting UFW to Defaults

If your rules become tangled or you want to start fresh:

sudo ufw reset

This deletes all rules and disables UFW. You'll need to set up default policies and rules from scratch. On a remote server, make sure you have console access before resetting — if UFW re-enables with no SSH rule, you're locked out.

Complete UFW Setup for a Production Web Server

Here is the full sequence I run on every new Ubuntu server. Copy-paste ready, with comments explaining each step:

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH with rate limiting
sudo ufw limit 22/tcp comment 'SSH rate limit'

# Allow web traffic
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Enable logging
sudo ufw logging medium

# Enable firewall
sudo ufw --force enable

# Verify
sudo ufw status verbose

That's six commands for a solid firewall baseline. Add IP-specific rules for databases or admin panels as needed. Review with ufw status numbered monthly and remove rules for services you no longer run. A firewall with 30 rules for 3 services is worse than no firewall — it gives you false confidence while being impossible to audit.

For the complete server security picture — SSH hardening, automatic updates, Fail2Ban, and kernel tuning — see the Linux server hardening checklist or the secure Linux server setup guide.