> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# UFW Firewall Rules That Don't Lock You Out
- URL: https://blog.sudowl.com/ufw-firewall-rules-ubuntu/
- Published: 2026-04-03T10:00:00.000Z
- Updated: 2026-10-11T08:47:37.000Z
- Description: UFW on Ubuntu and Debian without the lockout: SSH first, a dead-man switch, rule order, subnet and game ports, and the Docker bypass.
- Author: Lukas
- Tags: Linux, Debian, Ubuntu

UFW is the firewall I put on every Ubuntu and Debian box. Not because it's the most powerful thing around — it's a frontend for iptables/nftables, nothing more — but because the rules read like English and I can audit a box in five seconds with `ufw status`. The only real way to hurt yourself with it is the order you do things in. So this post is mostly about order.

**TL;DR:** Allow SSH *before* you enable, set default deny incoming, then enable. Everything else is details.

```bash
sudo apt install ufw            # Debian doesn't ship it, Ubuntu does
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH          # or: sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

```

That's a working baseline. Below is how I add rules without ever cutting the branch I'm sitting on.

## The order that matters

`ufw enable` applies the rules immediately to the running kernel, including to your current SSH session. If SSH isn't allowed at that moment, your session dies and you're on the provider's web console. UFW even warns you (`Command may disrupt existing ssh connections`) and people still hit `y`.

Before enabling, check what's queued:

```bash
sudo ufw show added

```

This works while UFW is still inactive and lists every rule you've added. If there's no SSH rule in that list, don't enable.

`OpenSSH` is an application profile that ships with the `openssh-server` package. `sudo ufw app list` shows what's available — after installing nginx you also get `Nginx HTTP`, `Nginx HTTPS` and `Nginx Full`. If you moved SSH to another port, the `OpenSSH` profile is useless (it means 22), so allow the real port:

```bash
sudo ufw allow 2222/tcp comment 'ssh'

```

And if you're *about to* change the SSH port: open the new port first, restart SSH, log in on the new port from a second terminal, then remove the old rule. Never the other way round. The [SSH hardening checklist](/ssh-hardening-ubuntu-checklist/) walks through that.

## A dead-man switch for risky changes

When I'm changing rules on a remote box and I'm not 100% sure, I schedule UFW to turn itself off in five minutes:

```bash
sudo systemd-run --on-active=5m /usr/sbin/ufw disable

```

That prints the name of a transient timer, something like `Running timer as unit: run-r3f2a9c1d.timer`. If everything still works after the change, cancel it:

```bash
sudo systemctl stop run-r3f2a9c1d.timer   # use the exact name it printed

```

If you locked yourself out, wait five minutes and you're back in with the firewall off. Not pretty, but way better than a rescue boot. `echo "ufw disable" | sudo at now + 5 minutes` does the same if you have `at` installed.

## Everyday rules

```bash
# web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# or both at once once nginx is installed:
sudo ufw allow 'Nginx Full'

# a game server — ranges need an explicit protocol
sudo ufw allow 25565/tcp comment 'minecraft'
sudo ufw allow 2456:2458/udp comment 'valheim'

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

# one admin IP gets everything
sudo ufw allow from 203.0.113.10

# SSH with built-in rate limiting — use INSTEAD of the plain allow
sudo ufw limit 22/tcp
sudo ufw delete allow OpenSSH

```

`limit` denies an IP that opens 6 or more connections within 30 seconds. It's crude but it takes the edge off brute-force noise. For real ban logic use [fail2ban](/fail2ban-setup-ssh-nginx/) or CrowdSec on top.

The `comment` bit is optional but I always add it on anything that isn't obvious. Six months later `25565/tcp` is obvious, `27015/udp` on a box running three game servers is not.

## Rule order: first match wins

UFW checks rules top to bottom and the first match decides. That matters as soon as you mix allow and deny. Say you have `allow 22/tcp` and later want to block one abusive IP:

```bash
sudo ufw deny from 198.51.100.7     # appended at the END — too late, 22 is already allowed above

```

That deny never fires for SSH because the allow above it matches first. Insert it at the top instead:

```bash
sudo ufw insert 1 deny from 198.51.100.7

```

Deleting rules, the readable way:

```bash
sudo ufw status numbered
sudo ufw delete 4
# or by spec, no number guessing:
sudo ufw delete allow 8080/tcp

```

Careful with `delete <number>` in a row — the numbers shift after every delete. Re-run `status numbered` each time, or delete from the bottom up.

## Gotcha: Docker walks straight past UFW

This is the big one. When you publish a container port with `-p 8080:80` or `ports:` in compose, Docker writes its own iptables rules in the `nat` table and the `DOCKER` chain. Traffic to that port gets forwarded to the container *before* UFW's input rules ever see it. `ufw status` says 8080 is closed, the internet says 8080 is open.

What I do: never publish container ports on all interfaces. Bind them to localhost and put a reverse proxy in front:

```yaml
services:
  app:
    image: myapp:latest
    ports:
      - "127.0.0.1:8080:80"

```

Then nginx on the host proxies to `127.0.0.1:8080` — that's the setup in [my nginx reverse proxy post](/nginx-reverse-proxy-setup/). If a port really must be public, publish it on purpose and know that UFW is not the thing protecting it. There's also the `DOCKER-USER` chain and the `ufw-docker` helper project if you want UFW-style control over published ports, but localhost binding is simpler and harder to get wrong. More on the Docker side in the [Docker install post](/docker-install-ubuntu/).

## Gotcha: IPv6 is a second firewall

UFW manages IPv4 and IPv6 rules together as long as `/etc/default/ufw` has:

```bash
IPV6=yes

```

That's the default on current Ubuntu and Debian. If someone set it to `no`, your v6 address is wide open while the v4 rules look perfect. Check `ufw status` — every rule should show up twice, once with `(v6)`. If you change this setting, `sudo ufw reload` afterwards.

## Gotcha: routed traffic needs its own rules

If the box forwards traffic — a WireGuard server, a NAT gateway, a host for VMs — the default forward policy in `/etc/default/ufw` is `DEFAULT_FORWARD_POLICY="DROP"`. Your clients connect fine and then reach nothing. Either set it to `ACCEPT` or, better, allow only what you need:

```bash
sudo ufw route allow in on wg0 out on eth0

```

Swap `eth0` for your real uplink (`ip -br link` tells you). The [WireGuard setup](/wireguard-vpn-server-ubuntu/) has the rest of that picture.

## Gotcha: the provider firewall is a separate layer

Many cloud providers have their own firewall in front of the VM. If a port is allowed in UFW and still unreachable, test from the box itself first:

```bash
sudo ss -tulpn | grep 8080          # is anything even listening?
curl -I http://127.0.0.1:8080       # does it answer locally?

```

Listening locally, allowed in UFW, unreachable from outside → it's upstream, not UFW. Also check the service listens on `0.0.0.0` or the public IP, not `127.0.0.1`.

## Logging and debugging

```bash
sudo ufw logging low

```

On Ubuntu with rsyslog the blocks land in `/var/log/ufw.log`. Debian 12 and newer don't install rsyslog by default, so read them from the kernel log instead:

```bash
sudo journalctl -k | grep 'UFW BLOCK'

```

Each line has `SRC=`, `DST=`, `PROTO=` and `DPT=` — enough to see whether the thing you think is blocked is actually the thing being blocked. Don't leave logging on `high` or `full` on a busy box; it fills the disk faster than you'd think.

## Starting over

If the ruleset became a mess:

```bash
sudo ufw reset

```

That disables UFW and deletes all rules (it keeps backups of the old rule files in `/etc/ufw/`). Then re-add SSH *first* and work through the TL;DR again. Same rule as always: SSH in, check `show added`, then enable.

This firewall baseline is step one of my [initial server setup checklist](/ubuntu-server-initial-setup-checklist/) — everything else on a fresh box comes after it.

---

### Related posts

- [Linux Log Rotation with logrotate — Before Your Disk Fills Up](https://blog.diengdoh.com/logrotate-linux-log-rotation/?ref=blog.sudowl.com)
- [Linux User Management — adduser, Groups, sudo and SSH Keys](https://blog.diengdoh.com/linux-user-management-adduser-sudo-ssh-keys/?ref=blog.sudowl.com)
- [Set Up Automatic Security Updates on Ubuntu and Debian](https://blog.diengdoh.com/ubuntu-automatic-updates-unattended-upgrades/?ref=blog.sudowl.com)