Set Up a Firewall on Linux: ufw, firewalld and nftables

Short answer
# 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
DistroDefault toolState after install
UbuntuufwInstalled, inactive
Linux Mintufw + gufw (GUI)Installed; check with sudo ufw status and enable it if it says inactive
DebiannoneNo firewall; install ufw or firewalld
FedorafirewalldActive (Workstation zone opens SSH and high ports)
openSUSE Leap / TumbleweedfirewalldActive
ArchnoneNo 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.

Remote machine? Allow SSH before enabling anything

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.

Fedora Workstation is deliberately permissive

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-USER chain, which Docker leaves for you, as described in Docker's packet-filtering documentation.
  • With firewalld, Docker adds a docker zone; check it with sudo 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

SymptomCause and fix
Locked out of SSH after enablingNo 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 serviceFirewall dropping packets (on the host, router or cloud security group). Check each layer.
No route to host although ping worksfirewalld 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 ignoredfirewalld or another tool also active. sudo systemctl disable --now firewalld and keep one.
Docker port open despite deny ruleDocker'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.