Terminal showing ufw status verbose output after setting up a UFW firewall on Ubuntu

UFW Firewall on Ubuntu: Set It Up Without Losing SSH

Spread the love

Turning on a firewall is easy. The tricky part is doing it on a remote server without cutting off the SSH session you’re typing into. Setting up a UFW firewall on Ubuntu takes about five commands. Get the order wrong and you’ll be logging into your VPS provider’s web console to undo it. This guide puts the steps in the right order and covers the one big gotcha most tutorials skip: Docker.

Terminal showing ufw status verbose output after setting up a UFW firewall on Ubuntu

Allow OpenSSH Before You Type “ufw enable”

UFW (Uncomplicated Firewall) ships with Ubuntu but is inactive by default. When you enable it, the default policy blocks incoming connections. If you haven’t allowed SSH yet, your next keystroke may never arrive. So the first rule always goes in while the firewall is still off:

sudo ufw status
sudo ufw allow OpenSSH
sudo ufw show added

OpenSSH is an application profile, so you don’t have to remember port numbers. Running ufw show added lists the rules you’ve queued while UFW is still off. Check that SSH is in that list before you go any further.

Here’s a mistake people make all the time: they moved sshd to a custom port like 2222 months ago, and the OpenSSH profile only opens port 22. If that’s your setup, allow the real port instead:

sudo ufw allow 2222/tcp

If you still log in with a password, now is a good time to switch to SSH keys instead of passwords. A firewall controls who can reach port 22. Keys control who can get in once they do.

Setting Default Deny Incoming and Turning It On

On Ubuntu the defaults are usually already deny incoming and allow outgoing. Set them explicitly anyway so you know exactly where you stand:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

UFW will warn you that enabling it may disrupt existing SSH connections. Type y. Then, before you close anything, open a second terminal and start a new SSH session. Your current session survives because it’s already established, so it proves nothing. Only a fresh login shows the rule actually works. If the new session hangs, use the original one to run sudo ufw disable and figure out what went wrong.

Opening Specific Ports and App Profiles

Only open what you actually run. To see which profiles your installed packages registered:

sudo ufw app list
sudo ufw app info "Nginx Full"

Some common rules:

sudo ufw allow "Nginx Full"            # ports 80 and 443
sudo ufw allow 443/tcp                 # just HTTPS
sudo ufw allow 51820/udp               # WireGuard
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp

The last rule allows SSH only from your home LAN. That’s handy for a home server, as long as the rest of your network is locked down too. If it isn’t, work through these router settings to lock down.

Always add /tcp or /udp when you know which protocol you need. A bare ufw allow 53 opens both protocols, which is often more than you meant to open.

Rate Limiting SSH With “ufw limit”

Any server with port 22 open to the internet gets hit by password-guessing bots. UFW has a simple built-in defence:

sudo ufw limit OpenSSH

According to the UFW documentation, limit blocks an IP address that tries to open 6 or more connections within 30 seconds. That’s enough to slow down a brute-force script, and it won’t bother a normal person logging in. One trade-off: scripts that open many short SSH connections in a row, like some Ansible runs or rsync loops, can trip the limit. If your deploys suddenly start timing out, check this first.

If you ran allow OpenSSH earlier, the limit command updates that existing rule instead of adding a duplicate. Confirm it in the status output.

Reading ufw status verbose and Deleting Rules by Number

sudo ufw status verbose
sudo ufw status numbered

The verbose view shows three lines that matter at the top: Status: active, the logging level, and the default policies (for example “deny (incoming), allow (outgoing), disabled (routed)”). Below that, each rule appears twice if IPv6 is enabled, once plain and once marked (v6). That’s normal.

The numbered view is how you remove rules safely:

sudo ufw delete 4

Watch out for this: after each deletion, the remaining rules get renumbered. If you want to remove rules 4 and 6, delete 6 first and then 4. Or simply run status numbered again before each delete. You can also delete by the rule itself, for example sudo ufw delete allow 8080/tcp, which avoids the numbering problem altogether. And since UFW checks rules in order and the first match wins, use sudo ufw insert 1 deny from 203.0.113.7 when you need a block to apply before an existing allow rule.

Logging, and Why Docker Ignores Your UFW Firewall on Ubuntu

Turn logging on at the low level. That records blocked packets without filling your disk:

sudo ufw logging low
sudo tail -f /var/log/ufw.log
sudo journalctl -k | grep UFW

Look for lines tagged [UFW BLOCK]. Higher levels like medium or high log a lot more. If you raise the level for troubleshooting, keep an eye on the size of /var/log, and see these safe Linux disk cleanup steps if the logs balloon.

Now for the Docker caveat. When you publish a container port with -p 8080:80, Docker writes its own iptables rules in the NAT and FORWARD chains. That traffic never reaches the INPUT rules UFW manages. The result: ufw status says 8080 is closed, but the whole internet can still reach it. A UFW firewall on Ubuntu simply doesn’t see that path. The simplest fix is to bind published ports to localhost and put a reverse proxy in front:

docker run -p 127.0.0.1:8080:80 nginx
# or in docker-compose.yml
ports:
  - "127.0.0.1:8080:80"

Advanced users can add filtering rules to Docker’s DOCKER-USER chain instead. The Ubuntu community UFW documentation covers the before.rules and after.rules files, which is where those custom iptables tweaks go. Either way, test from a machine outside your network rather than trusting the status output.

If you only remember one thing: queue sudo ufw allow OpenSSH (or your real custom port) and confirm it with ufw show added before you ever type enable. Then test from a second terminal, and bind every Docker port to 127.0.0.1 unless you really mean to expose it.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top