Every Ubuntu server you spin up ships with a fully capable firewall installed — and completely switched off. That firewall is UFW (Uncomplicated Firewall), and enabling it is one of the highest-value five minutes you can spend on a server. This guide walks you through setting up UFW on Ubuntu 24.04 and 22.04: a default-deny policy, allowing only the services you actually run, and locking down SSH without ever getting locked out yourself.
You will learn: how to check UFW status, set safe defaults, allow/deny specific ports, use application profiles, rate-limit SSH against brute force, manage IPv6 rules, and recover if something goes wrong. No prior firewall experience needed — just an Ubuntu server you can SSH into.
Prerequisites
- An Ubuntu 22.04 or 24.04 server (works on Debian 11/12 too).
- SSH access with a non-root user that has sudo privileges.
- Know which services your server runs (SSH on port 22 is almost certain; a web server means 80/443; a database usually means nothing should be open to the world).
Warning: If you are connected over SSH, your very first rule must allow SSH. If you enable UFW with a default-deny incoming policy before allowing port 22, you will lock yourself out instantly. The order in this guide prevents that.
Step 1 — Check UFW status and install if needed
UFW comes pre-installed on Ubuntu, but let's confirm and make sure it's current:
sudo ufw status verbose
If UFW isn't installed you'll see command not found. Install it:
sudo apt update
sudo apt install ufw -y
A fresh install reports Status: inactive. That's expected — we configure the rules before we flip the switch.
Step 2 — Set safe default policies first
Defaults define what happens to traffic that matches no explicit rule. The sane posture for a server: deny everything incoming, allow everything outgoing. Outgoing connections (your server fetching updates, talking to APIs) keep working; nothing unsolicited gets in.
sudo ufw default deny incoming
sudo ufw default allow outgoing
Each command prints Default incoming policy changed to 'deny' and similar for outgoing. Run these before adding any rules — order matters only at enable time, but setting defaults first keeps your thinking straight.
Step 3 — Allow SSH before enabling (don't skip this)
Allow SSH so your current session survives the moment you enable the firewall. Use the service name — UFW knows common ports:
sudo ufw allow ssh
This opens TCP port 22. If your SSH runs on a custom port (say 2222), allow that instead:
sudo ufw allow 2222/tcp
Confirm SSH is in the rule list:
sudo ufw show added
You should see your SSH rule listed. Only proceed when it is — this is your lifeline.
Step 4 — Enable the firewall
Now flip the switch:
sudo ufw enable
UFW warns: Command may disrupt existing ssh connections. Proceed with operation (y|n)? — type y. Because you allowed SSH first, your session continues uninterrupted. Verify:
sudo ufw status verbose
You should see Status: active, your SSH rule, and the default policies. From this moment, every port you haven't explicitly opened is closed to the world.
Step 5 — Allow the services you actually run
Now open ports selectively. The pattern is always sudo ufw allow <port>/<proto> or sudo ufw allow <service>. Common ones:
Web server (HTTP/HTTPS)
sudo ufw allow http
sudo ufw allow https
This opens TCP 80 and 443. If you only serve HTTPS, skip the http line — but keep port 80 open if you use Let's Encrypt HTTP-01 challenges for certificates.
Allow from a specific IP (databases, admin panels)
MySQL, PostgreSQL, Redis, and admin dashboards should never be open to the whole internet. Restrict them to your office or home IP:
sudo ufw allow from 203.0.113.45 to any port 3306
This opens MySQL (3306) only to 203.0.113.45. Replace the IP with your own (find it with curl ifconfig.me from your local machine). Same pattern for PostgreSQL (5432) or Redis (6379).
Restrict to a specific interface (private networks)
If your VPS provider gives you a private network (e.g. eth1 at 10.0.0.0/8), let internal traffic flow freely there while keeping it off the public internet:
sudo ufw allow in on eth1 from 10.0.0.0/8
Step 6 — Use application profiles instead of raw ports
Packages like Nginx and OpenSSH register UFW "app profiles" — named bundles of ports. They're cleaner than memorizing port numbers:
sudo ufw app list
Typical output includes OpenSSH, Nginx Full (80+443), Nginx HTTP (80 only), and Nginx HTTPS. To allow the full Nginx profile:
sudo ufw allow "Nginx Full"
Note the quotes — profile names with spaces need them. You can inspect what a profile opens with sudo ufw app info "Nginx Full".
Step 7 — Rate-limit SSH against brute-force attacks
Port 22 is the most-scanned port on the internet. UFW's limit rule throttles repeat connection attempts — typically allowing 6 connections in 30 seconds from one IP, then blocking that IP temporarily:
sudo ufw limit ssh
Or for a custom port:
sudo ufw limit 2222/tcp
Delete the plain allow ssh rule afterward so the limit rule is the one in effect — UFW picks the first matching rule, so make sure limit appears above any plain allow for the same port (see Step 9 on rule ordering).
Step 8 — Deny specific hosts and handle IPv6
Block a hostile IP
sudo ufw deny from 198.51.100.23
This drops all traffic from that address. UFW evaluates deny rules alongside allow rules, so a deny for a specific IP takes effect for that IP even if a broader allow exists.
Check IPv6 coverage
UFW manages IPv4 and IPv6 together by default. Confirm it's enabled in the config:
grep IPV6 /etc/default/ufw
You want IPV6=yes. With it on, every rule you add applies to both stacks automatically — check with sudo ufw status numbered and look for (v6) entries.
Step 9 — Review, reorder, and remove rules
List rules with numbers so you can manage them precisely:
sudo ufw status numbered
Example output:
[ 1] 22/tcp LIMIT IN Anywhere
[ 2] 80,443/tcp ALLOW IN Anywhere
[ 3] 3306 ALLOW IN 203.0.113.45
[ 4] Anywhere DENY IN 198.51.100.23
Delete a rule by number (say you no longer need the HTTP profile):
sudo ufw delete 2
Insert a rule at a specific position — UFW matches top-down, first match wins, so order matters for overlapping rules:
sudo ufw insert 1 allow from 203.0.113.45 to any port 22
This puts a trusted-IP SSH exception above the general limit rule. Re-run sudo ufw status numbered after any change to confirm the order.
Verify it worked
Run these checks from another machine (not the server itself), since UFW rules don't apply to loopback traffic the same way:
- Status overview:
sudo ufw status verboseon the server — confirmStatus: activeand the expected rules. - Port scan: from your local machine,
nmap -Pn -p 22,80,443,3306 your-server-ip. Only the ports you allowed should showopen; the rest should befiltered. - SSH still works: open a new SSH session (don't close your current one until the new one connects).
- Log watch: blocked packets are logged.
sudo grep UFW /var/log/ufw.log | tailshows what's being dropped — a healthy sign of probes hitting a closed door.
Common issues & fixes
1. Locked out of SSH after enabling UFW
Symptom: SSH times out right after ufw enable.
Cause: You enabled before allowing SSH, or allowed the wrong port.
Fix: Use your provider's web console (VNC/serial console) to log in directly, then run sudo ufw allow ssh and sudo ufw reload. Prevention: always run sudo ufw show added and confirm SSH is listed before ufw enable.
2. "ERROR: problem running ufw-init" on enable
Symptom: sudo ufw enable fails with an iptables error.
Cause: Usually a VPS with a custom kernel missing iptables modules, or a conflict with another firewall tool like firewalld.
Fix: Check which tool your system uses — on RHEL-family systems UFW isn't the default anyway. On Ubuntu VPSes, ask your provider to enable the required kernel modules, or disable the conflicting service first (sudo systemctl stop firewalld).
3. Rules added but traffic still blocked
Symptom: ufw allow 8080 but the app is unreachable.
Cause: Either the app binds only to 127.0.0.1 (check with ss -tlnp), or a higher-numbered DENY rule matches first, or your cloud provider's security group is blocking it upstream.
Fix: Check the app's bind address, review rule order with ufw status numbered, and remember UFW sits inside the server — AWS/GCP/Azure security groups still apply outside it.
4. IPv6 rules missing — (v6) entries absent
Symptom: ufw status shows no (v6) rules.
Cause: IPV6=no in /etc/default/ufw.
Fix: Set IPV6=yes, then sudo ufw disable && sudo ufw enable (with SSH allowed first — a disable/enable cycle briefly drops protection) to regenerate the rule set.
5. UFW blocks Docker containers' outbound traffic
Symptom: Containers can't reach the internet after enabling UFW.
Cause: Docker manipulates iptables directly, bypassing/conflicting with UFW's chains.
Fix: The widely used workaround is setting DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw and adding the Docker bridge subnet to allowed forwarding, or managing Docker host ports through UFW explicitly. This is a known rough edge — search for the current community guide for your Docker version before going to production.
Conclusion
That's a production-ready UFW setup: default-deny incoming, SSH protected and rate-limited, only the services you run exposed, databases pinned to trusted IPs, and IPv6 covered. Revisit your rules quarterly — servers accumulate forgotten open ports the way desks accumulate papers. A quick sudo ufw status numbered every few months keeps the attack surface exactly where you intended.
Need help hardening a production server? XpertBees sets up and secures Ubuntu servers, firewalls, and deployments for clients — reach out and we'll handle it.



