Set Up a Firewall with UFW on Ubuntu and Debian
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.
- Step 1: Check and Install UFW
- Step 2: Set the Default Policies
- Step 3: Allow SSH — Before You Enable Anything
- Step 4: Enable the Firewall
- Step 5: Allow Common Services
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
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:
sudo ufw statusOn 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:
sudo apt update
sudo apt install -y ufwUFW 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:
sudo ufw default deny incoming
sudo ufw default allow outgoingdeny 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 enablewhile the default isdeny incomingand 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:
sudo ufw allow OpenSSHOpenSSH 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:
sudo ufw allow 22/tcpIf your SSH daemon listens on a non-standard port (say 2222), allow that instead — the OpenSSH profile only covers 22:
sudo ufw allow 2222/tcpBetter 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:
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:
sudo ufw enableUFW 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:
sudo ufw status verboseYou 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:
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPSBy service name (UFW resolves these from /etc/services):
sudo ufw allow http
sudo ufw allow httpsBy application profile — many packages register their own when installed:
sudo ufw app list
sudo ufw allow 'Nginx Full' # opens 80 and 443 in one ruleFor a port range, you must specify the protocol:
sudo ufw allow 6000:6007/tcpAnd UDP works the same way — for example, a WireGuard endpoint:
sudo ufw allow 51820/udpStep 6: Restrict Rules by Source IP
Deny-by-default is good; scoping sensitive ports to known sources is better. Allow one IP full access:
sudo ufw allow from 203.0.113.10Lock SSH down to a single office IP (a strong hardening step for admin ports):
sudo ufw allow from 203.0.113.10 to any port 22 proto tcpAllow a whole internal subnet to reach a database port, but nobody else:
sudo ufw allow from 10.0.0.0/24 to any port 3306 proto tcp # MySQL from the internal network onlyExplicitly block a bad actor:
sudo ufw deny from 198.51.100.5UFW 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:
sudo ufw status verboseList rules with index numbers (you'll need these to delete by number):
sudo ufw status numberedTurn on logging so you can see what's being blocked:
sudo ufw logging on # 'low' level
sudo ufw logging medium # more detail when troubleshootingUFW writes to /var/log/ufw.log (and the system log). Watch it live while you test:
sudo tail -f /var/log/ufw.logStep 8: Edit and Delete Rules
Delete by mirroring the original rule spec:
sudo ufw delete allow 80/tcpOr delete by number — safest for compound rules. List first, then remove:
sudo ufw status numbered
sudo ufw delete 3Insert a rule at a specific position so it's evaluated before others:
sudo ufw insert 1 deny from 198.51.100.5After hand-editing config files (see the IPv6 note below), reload to apply without dropping active connections:
sudo ufw reloadCommon 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 addsudo ufw allow OpenSSHandsudo 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:80is reachable from the internet even if UFW denies port 80. Fixes: bind published ports to localhost (-p 127.0.0.1:80:80), use theufw-dockerhelper, or add rules to theDOCKER-USERchain. Don't assume UFW protects container ports. - IPv6 rules seem to be missing. Ensure
IPV6=yesin/etc/default/ufw(the default on modern Ubuntu/Debian), thensudo 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:
sudo ufw disableTo wipe all rules and return to defaults (prompts for confirmation):
sudo ufw resetreset 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:
sudo apt remove --purge ufwOfficial References
- UFW — Ubuntu Server documentation — the canonical guide
- Debian Wiki: Uncomplicated Firewall (ufw) — Debian-specific notes
man ufw— the full rule syntax reference, always available on the box- ufw-docker — the standard fix for the Docker-bypasses-UFW problem
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.