By default, your server accepts connections on all open ports. A firewall turns that around: it blocks everything and only lets through what you explicitly allow. UFW ("Uncomplicated Firewall") does this with a few, readable commands β the perfect first line of defense.
What are we building?
By the end, UFW runs on your Debian 13 with the base rule "block all incoming", with only SSH (your access) plus HTTP/HTTPS (ports 80/443, which you'll need later for Traefik) open. Outgoing connections stay allowed. The key part: we proceed in a way that ensures you don't lock yourself out.
Prerequisites
- A hardened server with a sudo user
- You know which port your SSH runs on (default: 22)
Step by step
Step 1: Install UFW
sudo apt update
sudo apt install -y ufw
Check which version you got:
sudo ufw version
ufw 0.36.2
Copyright 2008-2023 Canonical Ltd.
Right after installation UFW is still inactive β and that's intended: a packet filter that goes live without any allowances would lock you out. sudo ufw status confirms it with Status: inactive.
Step 2: Define the base rules
First the default direction: deny everything incoming, allow everything outgoing.
sudo ufw default deny incoming
sudo ufw default allow outgoing
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
These rules don't take effect yet β UFW is still inactive. So the exceptions come now, before we switch it on.
Step 3: Allow SSH β first, or you'll lock yourself out
π Allow SSH first, then activate
If you enable UFW with the "deny incoming" rule without having allowed SSH first, the next command cuts your own connection β and you can no longer get in via SSH. This order is non-negotiable.
If your SSH runs on the default port 22 (and openssh-server is installed), there's a ready-made profile for it:
sudo ufw allow OpenSSH
If you changed the SSH port (e.g. to 2222), allow exactly that port instead:
sudo ufw allow 2222/tcp
Step 4: Allow HTTP and HTTPS
For the later reverse proxy with Traefik you need the web ports. Open them right away:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
UFW confirms each rule with Rules updated and Rules updated (v6) (the IPv6 variant).
Step 5: Activate the firewall and check it
Now switch it on:
sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Check the result:
sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6)
Status: active and your three allowances β done. To be safe, test in a second SSH session that you can still connect before closing the first one.
The disabled (routed) in the Default line is normal on a fresh Debian: UFW only filters forwarded traffic if IP forwarding is enabled in the kernel at all. Once you install Docker later β which turns on net.ipv4.ip_forward β it will read deny (routed) instead. Both are fine, they just describe different starting points.
When things go wrong
After ufw enable you can no longer get in via SSH. The SSH rule was missing or targeted the wrong port. Connect via the console in the netcup SCP (VNC, independent of SSH), allow your SSH port there (sudo ufw allow β¦) and test again. That's exactly what step 3 warns about.
ERROR: Could not find a profile matching 'OpenSSH'. The OpenSSH profile only exists if openssh-server is installed. Use the port number instead: sudo ufw allow 22/tcp (or your port). sudo ufw app list shows the available profiles.
A Docker container is reachable from outside even though UFW doesn't open the port. Not your mistake β Docker bypasses UFW. Docker writes its rules directly into iptables and inserts them ahead of the UFW chains. A published container port (ports: in the compose.yaml) is thus open, no matter what UFW says. The clean solution is a second firewall layer in front of the server β at netcup the firewall in the SCP as an upstream perimeter. On the host it also helps to bind container ports only to 127.0.0.1 instead of 0.0.0.0.
Maintenance & backups
-
View and delete rules:
sudo ufw status numberednumbers all rules;sudo ufw delete 3removes rule 3. That's how you tidy up when a service goes away. - New services: For every additional public port, one targeted rule β never "just open everything". What doesn't need to be open stays closed.
- No backup needed, but note down your allowances (or keep them in the same document as your DNS records). The rule set is retyped in seconds.
- Know the limits: UFW protects the host, but not against the Docker gap above. An upstream perimeter firewall like the netcup firewall in the SCP complements UFW into two layers that secure each other β ideal once containers with open ports are running.
This post first appeared on serverkueche.de.
Top comments (0)