Security

Set Up a Firewall with UFW on Ubuntu and Debian

Beginner35 min to complete9 min readJuly 4, 2026

Quick answer

Lock down a Linux server with UFW in about half an hour. Set sane default policies, open only the ports you need, restrict access by source IP, and rate-limit SSH — without accidentally locking yourself out of the box.

beginner · 35 min

Before you begin

  • An Ubuntu (20.04+) or Debian (11+) server
  • A user with sudo privileges
  • SSH access — plus your cloud provider's serial/VNC console available as a safety net
  • Basic comfort with the Linux command line
Linux
UFW
Firewall
Ubuntu
Debian
Security
Networking

Every internet-facing Linux server needs a host firewall, but the raw tooling — iptables or nftables — is famously fiddly. UFW (Uncomplicated Firewall) is exactly what its name promises: a friendly front end to the same kernel netfilter engine, with a command syntax you can actually remember. It ships with Ubuntu (inactive by default) and installs in one line on Debian.

In this tutorial you'll take a fresh server from "everything is open" to a sane deny-by-default posture: block all inbound traffic, then explicitly allow only what you need, scope some rules to specific source IPs, and rate-limit SSH against brute-force. UFW writes standard netfilter rules under the hood — if you ever want to see what it's really doing, the iptables-to-nftables migration guide covers the layer beneath it, and our nftables cheat sheet is a quick reference for the raw syntax.

The one rule that matters more than any other: allow SSH before you enable the firewall. On a remote server, a deny-all policy with no SSH exception will drop your own connection and lock you out. We'll do it in the safe order, and cover recovery if it ever goes wrong.

What You'll Build

  • UFW installed and set to deny all incoming / allow all outgoing by default
  • An SSH allow rule (rate-limited) added before the firewall is enabled
  • Rules for common services — HTTP/HTTPS, port ranges, and UDP
  • Source-IP-restricted rules (e.g. SSH only from your office IP, a database port only from an internal subnet)
  • Logging turned on, and the skills to inspect, reorder, and delete rules

Step 1: Check and Install UFW

First see whether UFW is present and what state it's in:

bash
sudo ufw status

On Ubuntu you'll usually see Status: inactive — it's installed but not running. On a minimal Debian image the command may be missing, so install it:

bash
sudo apt update
sudo apt install -y ufw

UFW is a management layer, not a separate firewall — it compiles your simple rules into netfilter rules in the kernel. Nothing is enforced yet; the firewall is still inactive.

Step 2: Set the Default Policies

Start from a whitelist model — reject everything inbound, permit everything outbound:

bash
sudo ufw default deny incoming
sudo ufw default allow outgoing

deny incoming means any inbound packet with no matching allow rule is dropped. allow outgoing lets the server reach out (package updates, DNS, API calls) without you enumerating every destination. This is the standard baseline for a server; you'll poke specific holes for the services you actually run.

Step 3: Allow SSH — Before You Enable Anything

⚠️ Do not skip this. If you run ufw enable while the default is deny incoming and there's no SSH rule, the firewall will drop your current SSH session and refuse new ones. On a remote box with no console access, that means a rebuild. Add the SSH rule first.

Allow SSH using its application profile:

bash
sudo ufw allow OpenSSH

OpenSSH is a built-in profile that maps to port 22/tcp. You can see all registered profiles with sudo ufw app list. The explicit-port form is equivalent:

bash
sudo ufw allow 22/tcp

If your SSH daemon listens on a non-standard port (say 2222), allow that instead — the OpenSSH profile only covers 22:

bash
sudo ufw allow 2222/tcp

Better still, rate-limit SSH. limit allows the connection but blocks any source IP that makes 6 or more connection attempts within 30 seconds — a cheap, effective brake on brute-force bots:

bash
sudo ufw limit ssh

(Use limit ssh or allow ssh, not both for the same port — limit already permits normal traffic.)

Step 4: Enable the Firewall

With SSH allowed, turn it on:

bash
sudo ufw enable

UFW warns you and asks to confirm:

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Type y. Because you allowed SSH in Step 3, your session stays connected. Verify:

bash
sudo ufw status verbose

You should see Status: active, the default policies, and your SSH rule for both IPv4 and IPv6.

Step 5: Allow Common Services

Open the ports your applications need. By port and protocol:

bash
sudo ufw allow 80/tcp     # HTTP
sudo ufw allow 443/tcp    # HTTPS

By service name (UFW resolves these from /etc/services):

bash
sudo ufw allow http
sudo ufw allow https

By application profile — many packages register their own when installed:

bash
sudo ufw app list
sudo ufw allow 'Nginx Full'   # opens 80 and 443 in one rule

For a port range, you must specify the protocol:

bash
sudo ufw allow 6000:6007/tcp

And UDP works the same way — for example, a WireGuard endpoint:

bash
sudo ufw allow 51820/udp

Step 6: Restrict Rules by Source IP

Deny-by-default is good; scoping sensitive ports to known sources is better. Allow one IP full access:

bash
sudo ufw allow from 203.0.113.10

Lock SSH down to a single office IP (a strong hardening step for admin ports):

bash
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp

Allow a whole internal subnet to reach a database port, but nobody else:

bash
sudo ufw allow from 10.0.0.0/24 to any port 3306 proto tcp   # MySQL from the internal network only

Explicitly block a bad actor:

bash
sudo ufw deny from 198.51.100.5

UFW evaluates rules top to bottom and stops at the first match, so order matters — put specific rules (like a targeted deny) above broader allow rules. Step 8 shows how to insert a rule at a chosen position.

Step 7: Inspect Status, Rules, and Logging

See the full picture, including defaults and per-rule detail:

bash
sudo ufw status verbose

List rules with index numbers (you'll need these to delete by number):

bash
sudo ufw status numbered

Turn on logging so you can see what's being blocked:

bash
sudo ufw logging on       # 'low' level
sudo ufw logging medium   # more detail when troubleshooting

UFW writes to /var/log/ufw.log (and the system log). Watch it live while you test:

bash
sudo tail -f /var/log/ufw.log

Step 8: Edit and Delete Rules

Delete by mirroring the original rule spec:

bash
sudo ufw delete allow 80/tcp

Or delete by number — safest for compound rules. List first, then remove:

bash
sudo ufw status numbered
sudo ufw delete 3

Insert a rule at a specific position so it's evaluated before others:

bash
sudo ufw insert 1 deny from 198.51.100.5

After hand-editing config files (see the IPv6 note below), reload to apply without dropping active connections:

bash
sudo ufw reload

Common Issues

  • You locked yourself out of SSH. This happens when the firewall is enabled without an SSH allow rule. Recover through your cloud provider's serial/VNC console (AWS EC2 Serial Console, DigitalOcean/Hetzner console, Proxmox noVNC, etc.), log in locally, and run sudo ufw disable (or add sudo ufw allow OpenSSH and sudo ufw reload). Prevention: always allow SSH before enabling, and keep a second terminal open when testing new rules.
  • Docker publishes ports that UFW "blocks." This is the biggest UFW gotcha. Docker writes its own iptables rules and bypasses UFW, so a container started with -p 80:80 is reachable from the internet even if UFW denies port 80. Fixes: bind published ports to localhost (-p 127.0.0.1:80:80), use the ufw-docker helper, or add rules to the DOCKER-USER chain. Don't assume UFW protects container ports.
  • IPv6 rules seem to be missing. Ensure IPV6=yes in /etc/default/ufw (the default on modern Ubuntu/Debian), then sudo ufw reload. Otherwise your rules only apply to IPv4 while the box is still reachable over IPv6.
  • A cloud security group is also in play. Your provider's network firewall (AWS Security Groups, GCP firewall rules, etc.) sits in front of the instance and is a separate layer. Traffic must be permitted by both the security group and UFW. If a port looks blocked despite a UFW allow rule, check the security group too.

Frequently Asked Questions

Will enabling UFW disconnect my current SSH session?

Not if you add an SSH allow rule first. UFW does not tear down established connections when it activates, and with ufw allow OpenSSH (or ufw limit ssh) in place, both your existing session and new logins keep working. It only asks for confirmation because enabling a firewall can disrupt SSH if you haven't allowed it — which is exactly why Step 3 comes before Step 4.

Do I still need UFW if my cloud provider already has security groups?

They're complementary layers, and running both is good practice (defense in depth). A security group is a network-level firewall your provider enforces before traffic reaches the instance; UFW is a host-level firewall on the machine itself. UFW also protects against traffic between instances inside the same network and gives you host-specific controls (rate limiting, per-IP rules) that are quick to change without touching cloud config.

Why do my UFW rules not block Docker container ports?

Because Docker manipulates iptables directly and inserts its rules ahead of UFW's, so published ports (-p host:container) bypass UFW entirely. This is by design and catches many people out. Bind ports to 127.0.0.1, use a reverse proxy, adopt the ufw-docker project, or write rules in the DOCKER-USER chain — but never rely on a plain ufw deny to close a published container port.

What's the difference between ufw allow ssh and ufw limit ssh?

allow opens the port unconditionally. limit opens it too, but adds brute-force protection: if a single source IP makes six or more connection attempts within 30 seconds, UFW temporarily blocks it. For an admin port like SSH that's exposed to the internet, limit is the better default — you get the same access with a built-in brake on password-guessing bots.

Does UFW work the same on Ubuntu and Debian?

Yes — the commands, rule syntax, config files (/etc/default/ufw), and behavior are identical. The only practical difference is that Ubuntu ships UFW preinstalled (but inactive), whereas on a minimal Debian install you run sudo apt install ufw first. Everything after Step 1 in this tutorial is the same on both.

Tear Down

To stop enforcing rules but keep them for later:

bash
sudo ufw disable

To wipe all rules and return to defaults (prompts for confirmation):

bash
sudo ufw reset

reset disables the firewall before clearing rules, so it won't lock you out — but if you were relying solely on UFW (no cloud security group), confirm you still have a way back in first. To remove the package entirely:

bash
sudo apt remove --purge ufw

Official References

For the command-line fundamentals this builds on, see the advanced Linux commands tutorial.

We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.

Struggling with this in production?

We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.