A fresh Ubuntu Server install listens on more ports than most people expect. SSH is open, and anything you install later, a web server, a database, a VPN, tends to open its own door and leave it open. UFW, the Uncomplicated Firewall, is Ubuntu's friendly front end to the kernel firewall, and it turns "I should set up a firewall someday" into a ten-minute job. This guide covers a safe default setup for a remote server you reach over SSH, where one wrong order can lock you out of your own machine.
Before you start
You need a server running Ubuntu (20.04 or newer works the same way here), a non-root user with sudo, and an SSH connection that already works. If you are on a cloud VPS, note that your provider's security group or cloud firewall is a separate layer: UFW rules do not replace it, and it does not replace UFW. Both must allow SSH, or you are locked out anyway.
The warning that saves the most weekends: never enable UFW before explicitly allowing SSH. Its default policy denies all incoming traffic, so if you enable first and add the SSH rule second, your current session keeps working but the next login fails, and on a remote VPS that means a rescue console or a support ticket.
Step 1: Install UFW and check its state
UFW ships with Ubuntu Server by default, but check anyway:
sudo apt update
sudo apt install ufw -y
sudo ufw status verboseOn a fresh server the answer is Status: inactive: no rules enforced yet. Do not enable it yet.
Step 2: Set the default policies
Set the defaults before adding any allow rules, so every port you do not name explicitly stays closed:
sudo ufw default deny incoming
sudo ufw default allow outgoingThis is the sane default for a server: it can fetch updates and reach out, but nobody on the internet can initiate a connection to a port you have not opened. Run sudo ufw status verbose again and confirm the defaults read deny (incoming) and allow (outgoing).
Step 3: Allow SSH first, then verify it
This is the step that matters most. Allow SSH before anything else:
sudo ufw allow OpenSSHThe OpenSSH profile is just a named shortcut for port 22/tcp, kept in /etc/ufw/applications.d/openssh-server. If you moved SSH to a custom port, allow that port instead, for example sudo ufw allow 2222/tcp, and skip the profile. Confirm the rule is listed:
sudo ufw show addedYou should see a rule allowing 22/tcp (or your custom port). If you do not see it, stop here and fix it before continuing.
Step 4: Enable UFW
Now, and only now, enable the firewall:
sudo ufw enableUFW warns that enabling may disrupt existing SSH connections. That warning is about new connections, not your current one; your established session stays up. Still, do not close your terminal yet. Open a second terminal and start a new SSH session to the server. If the new login works, the firewall is correct. If it fails, your first session is still alive to fix the rules.
Then check the live state:
sudo ufw status verboseYou should see Status: active with the SSH rule and the default policies.
Step 5: Open only the ports you actually use
Add rules one service at a time. A web server needs HTTP and HTTPS (if you run Caddy, its automatic certificates still need those two ports reachable; see the Caddy web server playbook for the rest of that setup):
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpUFW also ships application profiles: list them with sudo ufw app list, then allow one by name, for example sudo ufw allow 'Nginx Full'. Profiles are convenient, but a port number is unambiguous; when in doubt, use the port.
For a VPN on the same box, the pattern is identical: allow the VPN's UDP port (for example, WireGuard's 51820/udp, as in the WireGuard VPN server guide) and nothing else. Every open port should map to a service you can name; if you cannot name it, close it.
Step 6: Rate-limit SSH against brute force
SSH on the open internet gets probed constantly. UFW can throttle repeat connection attempts from one address:
sudo ufw limit OpenSSHThis inserts a rate-limiting rule that refuses connections from an IP after several recent attempts. It is not a replacement for key-based login or for disabling password authentication, both of which belong in a full SSH hardening audit, but it cuts the noise dramatically, and it costs nothing.
Step 7: Manage rules without fear
Rules change as the server's job changes. To remove a rule, delete by number for precision:
sudo ufw status numbered
sudo ufw delete 2Deleting by number avoids the ambiguity of typing a rule twice. To see what the firewall is actually blocking, turn on logging at the low level, which records blocked packets without flooding the disk:
sudo ufw logging onCheck it with sudo grep UFW /var/log/ufw.log; entries marked [UFW BLOCK] are the firewall doing its job, and the first place to look when a new service cannot be reached.
To see the real listening state behind the firewall, compare UFW's view with the kernel's:
sudo ss -tulpnAnything in ss output that is not in your UFW rules is either localhost-only or a service you forgot to firewall.
Three traps that catch real servers
First, Docker bypasses UFW. Docker inserts its own iptables rules ahead of UFW's, so a container published with -p 8080:80 is reachable even if UFW denies 8080. If you run Docker on a UFW-protected host, either bind containers to localhost (-p 127.0.0.1:8080:80) or manage Docker's iptables integration explicitly.
Second, UFW and IPv6. UFW manages IPv6 rules only if IPV6=yes in /etc/default/ufw, which is the Ubuntu default. If someone set it to no, your IPv6 listeners are unfiltered. Check the file after any template change from a VPS provider.
Third, enabling UFW twice, or scripting ufw --force enable in a provisioning script before the SSH rule, reproduces the lockout from the introduction at scale. In automation, always order the playbook: defaults, SSH allow, then enable, then the rest.
Verify the whole setup
Before you call it done, run this checklist from a second machine, not just from the server itself:
- A new SSH login succeeds.
sudo ufw status verboseshowsStatus: active, SSH allowed, defaults deny incoming and allow outgoing.- A port scan of a closed port (for example with
nmap -Pn <server-ip>) shows it filtered, not open. - Your web service, if any, still answers on 80 and 443.
If all four pass, you have a firewall that does its job and stays out of your way. Revisit it whenever you install a new service: open its port deliberately, or leave it closed by default.
Sources
- Ubuntu Community Help Wiki, UFW: https://help.ubuntu.com/community/UFW
- UFW manual page: https://manpages.ubuntu.com/manpages/noble/en/man8/ufw.8.html
- Ubuntu Server Guide, firewall: https://documentation.ubuntu.com/server/how-to/security/firewalls/


