UFW Firewall on Ubuntu: Setup and Daily Use

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.

Scroll to Top