Firewall with UFW
Chapter 3 — Firewall with UFW
A firewall is the first thing that meets any incoming network connection. It decides, before any application sees the traffic, whether that connection should be allowed at all. Even though Cloudflare Tunnel handles your web traffic without opening inbound ports, a server-side firewall protects against attacks from within the local network, mis-configured services, and future changes to your setup. UFW — the Uncomplicated Firewall — provides a clean interface to iptables that's simple enough to set up correctly in minutes.
ufw limit. Managing rules with numbered list, delete, and insert. UFW logging. IPv6 behaviour. The minimal ruleset for a Cloudflare Tunnel server. What to do if you lock yourself out.
Why a Firewall Matters Even with Cloudflare Tunnel
The key insight: the Cloudflare Tunnel uses an outbound connection. UFW's default policy allows all outbound traffic, so the tunnel works perfectly with a strict inbound firewall. Web requests arrive from Cloudflare to localhost:80 — the loopback interface, which is treated separately from network interfaces. Meanwhile, the firewall blocks anything unexpected trying to reach the server from other machines on your home network.
- MySQL (port 3306) is bound to
127.0.0.1by default — good. But if it were ever misconfigured to0.0.0.0, UFW would block access from outside the server. - New services you install often bind to all interfaces. UFW denies them until you explicitly allow them.
- Defence in depth — the ISP blocks inbound, the router has its own firewall, and UFW adds a third layer on the server itself. Any one layer failing alone doesn't expose the server.
UFW Default Policies
Effect: any port not listed in your allow rules is silently blocked. Attackers scanning for open ports get no response.
Effect: you don't need to add rules for outbound traffic. The tunnel just works.
Check What's Listening Before You Start
Before enabling UFW, know what services are running and which ports they use. This prevents accidentally blocking something you need, and helps you identify services that shouldn't be exposed.
0.0.0.0 or * (not 127.0.0.1) is reachable from the network. Without UFW, that means reachable by anyone on your home network (or internet, if port forwarding exists). With UFW, they're blocked unless you add an allow rule. This is exactly the protection you want.
Safe Setup Order — SSH First, Then Enable
- Physical access to the server (plug in a keyboard and monitor)
- Or a remote console via your hosting provider (not applicable for a home server)
- Log in locally, run
sudo ufw allow ssh && sudo ufw reload
sudo ss -tlnp | grep ssh
Which Ports to Open for Your Setup
The right ruleset depends on how traffic reaches your server. With Cloudflare Tunnel, web traffic arrives via localhost (the loopback interface) — not from the network — so ports 80 and 443 don't need to be open in UFW for the tunnel to work.
ufw allow ssh or ufw allow 22/tcp.127.0.0.1 (already configured by default). Never open this to the network.sudo ufw allow ssh. That's genuinely all you need if all public-facing traffic goes through Cloudflare Tunnel. Everything else is denied. The tunnel (outbound) is unaffected. This is one of the cleanest possible firewall configurations.
UFW Rule Syntax
Application Profiles
UFW ships with named application profiles for common services — shortcuts that allow the right ports without needing to remember numbers.
ufw allow 'Apache Full'. Opening ports 80 and 443 to all sources is unnecessary — the tunnel delivers traffic via localhost. At most, allow those ports restricted to your local subnet. The only externally-visible benefit is that attackers can see your server has something running on 80/443, which is information you'd rather not give away.
Rate Limiting with ufw limit
ufw limit allows connections to a port but automatically blocks an IP that makes 6 or more connection attempts within 30 seconds. It's a quick defence against SSH brute-force attacks without the full weight of fail2ban (covered in Chapter 4).
ufw limit is simple — fixed threshold (6 attempts / 30 seconds), IPv4 and IPv6, built into UFW. fail2ban (Chapter 4) is more powerful — configurable thresholds, persistent bans, monitors log files, and covers Apache and other services too. Use both: limit for quick UFW-level protection now, fail2ban for deeper coverage later.
Managing Rules
View rules with numbers
Delete a rule by number
Block a specific IP address
Reset UFW (nuclear option — removes all rules)
UFW Logging
IPv6 Support
UFW handles IPv4 and IPv6 with the same rules by default — when you allow ssh, UFW automatically creates both an IPv4 and IPv6 rule. Verify this is enabled:
With IPV6=yes, every rule you add automatically gets an IPv6 counterpart. When you run sudo ufw status, you'll see rules listed twice — once for IPv4 and once as (v6) for IPv6. This is correct behaviour.
The Complete Ruleset for This Setup
https://osztromok.com from outside — if it loads, the tunnel is unaffected (as expected, since it uses outbound connections). Then try SSHing from another machine on the network — should succeed on port 22.
Troubleshooting
sudo ufw allow ssh && sudo ufw reload. (2) If you have another active SSH session open in a different terminal, use it immediately — run the allow command before it times out. (3) If the server is running in a VM with a hypervisor console, use the console to log in and fix the rule. Going forward: always allow SSH first, enable second.
allow outgoing policy covers. Check: (1) Did you accidentally change the outgoing default to deny? Run sudo ufw status verbose and look for Default: ... allow (outgoing). (2) Is cloudflared running? sudo systemctl status cloudflared. (3) The tunnel connects to Cloudflare's servers on port 443 (outbound) — this should always be allowed by the default outgoing policy.
ip route | grep proto kernel to get your subnet (e.g. 192.168.1.0/24), then: sudo ufw allow from 192.168.1.0/24 to any port 80 proto tcp and sudo ufw allow from 192.168.1.0/24 to any port 443 proto tcp. Verify with sudo ufw status that the rules were added, then reload: sudo ufw reload.
sudo systemctl status ufw — if it shows failed, there may be a conflict with iptables. Run sudo ufw enable again and look for error output. Also check: sudo iptables -L — if iptables rules already exist from another firewall tool (firewalld, nftables), they may conflict.
ufw limit bans your IP for 30 seconds after 6 connection attempts. Wait a minute and try again — the ban is temporary. If you keep triggering it, you may have an SSH client that retries aggressively. Switch to ufw allow ssh while debugging, then switch back to ufw limit ssh once the client behaviour is corrected. fail2ban (Chapter 4) has a whitelist mechanism for trusted IPs that UFW's built-in rate limiting lacks.
Quick Reference — Chapter 3
| Command | Purpose |
|---|---|
| sudo ufw status verbose | Show active rules, default policies, and logging level |
| sudo ufw status numbered | Show rules with numbers — needed for targeted deletion |
| sudo ufw default deny incoming | Block all inbound by default — set before enabling |
| sudo ufw default allow outgoing | Allow all outbound — required for tunnel, DNS, apt updates |
| sudo ufw allow ssh | Allow SSH — must run BEFORE ufw enable |
| sudo ufw limit ssh | Allow SSH with rate limiting (6 attempts / 30 seconds) |
| sudo ufw allow from 192.168.1.0/24 to any port 80 proto tcp | Allow port 80 from local network only |
| sudo ufw deny from IP | Block all traffic from a specific IP address |
| sudo ufw insert 1 deny from IP | Insert deny rule at top of list (evaluated first) |
| sudo ufw delete NUMBER | Delete rule by number (from ufw status numbered) |
| sudo ufw logging medium | Enable medium logging — blocked and allowed packets |
| sudo ufw reload | Reload rules without disabling — safe to run while active |
| sudo ufw reset | Wipe all rules and disable — use with caution |
| sudo ufw app list | List available named application profiles |
| Port / Service | Rule for Cloudflare Tunnel setup |
|---|---|
| 22/tcp (SSH) | ufw limit ssh — always open, rate-limited |
| 80/tcp (HTTP) | Optional: allow from local subnet only — not needed for tunnel |
| 443/tcp (HTTPS) | Optional: allow from local subnet only — not needed for tunnel |
| 3306/tcp (MySQL) | Never open — keep bound to 127.0.0.1 only |
| Everything else | Denied by default — don't open unless you have a specific reason |