Set Up a Firewall on Linux: ufw, firewalld and nftables
# Ubuntu, Debian, Linux Mint: ufw
sudo apt install ufw # preinstalled on Ubuntu and Mint
sudo ufw allow OpenSSH # do this first on remote machines
sudo ufw enable
sudo ufw status verbose
# Fedora, openSUSE: firewalld (already running)
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Default policy for both: block incoming connections you haven't allowed, allow all outgoing. Use one firewall tool per machine.
What you have now
sudo ufw status 2>/dev/null
sudo firewall-cmd --state 2>/dev/null
systemctl is-active ufw firewalld nftables 2>/dev/null
sudo nft list ruleset | head -40
# what's actually listening, and therefore worth protecting
sudo ss -tulpn
| Distro | Default tool | State after install |
|---|---|---|
| Ubuntu | ufw | Installed, inactive |
| Linux Mint | ufw + gufw (GUI) | Installed; check with sudo ufw status and enable it if it says inactive |
| Debian | none | No firewall; install ufw or firewalld |
| Fedora | firewalld | Active (Workstation zone opens SSH and high ports) |
| openSUSE Leap / Tumbleweed | firewalld | Active |
| Arch | none | No rules; choose nftables, ufw or firewalld |
All of them use the kernel's nftables framework underneath; ufw and firewalld are front-ends that write the rules for you. The iptables command on current distros is usually iptables-nft, a compatibility layer on top of nftables.
Enabling a firewall over SSH without an SSH rule disconnects you, and on a cloud VM without a console that can mean rebuilding it. Add the SSH rule first, keep your session open, and test a second login before closing it.
ufw on Ubuntu, Debian and Mint
Enable with sane defaults
sudo apt install ufw
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
Example output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
OpenSSH ALLOW IN Anywhere
OpenSSH (v6) ALLOW IN Anywhere (v6)
ufw starts automatically at boot once enabled.
Common rules
sudo ufw allow 80,443/tcp # web server
sudo ufw allow 8000:8100/tcp # a port range
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp # SSH from LAN only
sudo ufw limit OpenSSH # rate-limit repeated connections
sudo ufw deny from 203.0.113.7 # block one address
sudo ufw app list # profiles installed by packages
sudo ufw status numbered
sudo ufw delete 3 # remove rule number 3
sudo ufw reload
Rules are checked in order and the first match wins, so put specific deny rules before broad allow rules with sudo ufw insert 1 deny from ....
firewalld on Fedora and openSUSE
firewalld groups rules into zones. Each network interface belongs to one zone, and the zone decides which services are allowed in. Fedora Workstation uses FedoraWorkstation, Fedora Server uses FedoraServer, openSUSE uses public.
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all # active default zone
sudo firewall-cmd --get-services | tr ' ' '\n' | less
Open services and ports
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
--permanent writes to the saved configuration but doesn't change the running firewall until you --reload. Without it, the change is immediate but temporary, which is handy for testing. To save a working runtime setup: sudo firewall-cmd --runtime-to-permanent.
Restrict by source and use zones
# allow SSH only from the LAN using a rich rule
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept'
# or put the LAN in the trusted-ish "home" zone
sudo firewall-cmd --permanent --zone=home --add-source=192.168.1.0/24
# move an interface to another zone
sudo nmcli connection modify "Wired connection 1" connection.zone home
sudo firewall-cmd --reload
With NetworkManager, set the zone on the connection (as above) so it sticks across reconnects. Fedora's graphical tool is firewall-config; on openSUSE use YaST → Firewall.
The FedoraWorkstation zone allows incoming TCP and UDP on ports 1025–65535 plus SSH, so desktop apps work without prompts. If you want a stricter laptop setup, switch to the public zone: sudo firewall-cmd --set-default-zone=public, then add what you need.
nftables directly (Arch and minimal servers)
On Arch, the simplest robust option is plain nftables:
sudo pacman -S nftables
sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
meta l4proto { icmp, ipv6-icmp } accept
tcp dport 22 accept
# tcp dport { 80, 443 } accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
sudo nft -c -f /etc/nftables.conf # syntax check only
sudo systemctl enable --now nftables
sudo nft list ruleset
The inet table covers IPv4 and IPv6 together. Keep ICMP allowed: IPv6 breaks without it, and blocking ping makes troubleshooting harder for little gain. If you prefer a front-end, ufw and firewalld are both in the Arch repos. See the Arch Wiki on nftables.
Docker, VMs and other rule writers
Docker writes its own rules for published ports, and they're evaluated before ufw's input rules. A container started with -p 8080:80 is reachable from outside even if ufw says the port is denied.
- Publish internal services on localhost only:
-p 127.0.0.1:8080:80, and put a reverse proxy in front. - Or filter in the
DOCKER-USERchain, which Docker leaves for you, as described in Docker's packet-filtering documentation. - With firewalld, Docker adds a
dockerzone; check it withsudo firewall-cmd --zone=docker --list-all.
libvirt, LXD/Incus and Podman also add rules for their bridges. That's expected; don't flush the ruleset on a host running them. Setup details are in the Docker guide.
Verify from outside
Rules on the machine don't prove what's reachable. Test from another computer:
# from another machine on the network
nc -zv 192.168.1.50 22
nc -zv 192.168.1.50 8080
# more complete scan of your own server (only scan machines you own)
nmap -Pn 192.168.1.50
A port shows open (allowed and listening), closed (allowed but nothing listening, or rejected), or filtered (dropped by the firewall). On the server, watch blocked packets with sudo journalctl -k | grep -i 'UFW BLOCK' for ufw, or enable sudo firewall-cmd --set-log-denied=all temporarily for firewalld.
Common problems
| Symptom | Cause and fix |
|---|---|
| Locked out of SSH after enabling | No SSH rule. Use the provider's console, run sudo ufw allow OpenSSH or firewall-cmd --add-service=ssh --permanent, then reload. |
| Connection timed out to a service | Firewall dropping packets (on the host, router or cloud security group). Check each layer. |
| No route to host although ping works | firewalld rejects with an ICMP "host prohibited", which clients show as "No route to host". Open the port. |
| Rule added but nothing changed (firewalld) | Added with --permanent but no --reload, or added to a zone the interface isn't in. |
| ufw rules ignored | firewalld or another tool also active. sudo systemctl disable --now firewalld and keep one. |
| Docker port open despite deny rule | Docker's rules come first. Bind to 127.0.0.1 or use DOCKER-USER. |
The firewall cheat sheet shows ufw and firewall-cmd commands side by side, and the networking guide covers ss and other diagnostics.
Frequently asked questions
Does Ubuntu have a firewall enabled by default?
Ubuntu ships ufw but leaves it inactive. With no services listening on a fresh desktop, that's low risk, but enabling it with sudo ufw enable is a sensible default, especially on laptops.
Can I use ufw and firewalld together?
No. Both manage the same nftables/iptables rules and will conflict. Pick one per machine: ufw on Ubuntu/Debian/Mint, firewalld on Fedora/openSUSE.
Why can people reach my Docker container even though ufw blocks the port?
Docker inserts its own rules that are evaluated before ufw's. Publish ports only on localhost (-p 127.0.0.1:8080:80) or filter in the DOCKER-USER chain.
What's the difference between firewalld --permanent and runtime rules?
Runtime rules apply immediately and vanish on reload or reboot. --permanent rules are saved but only take effect after firewall-cmd --reload.
Spotted a mistake or a command that no longer works? Send a correction — pages are re-checked when reports come in.