Firewalls on Linux: ufw and nftables
Every Linux server should have a firewall. Even on a “private” cloud, ports left open are ports getting scanned. Linux’s kernel has a built-in firewall (called netfilter); the user-space tools to control it are iptables (legacy), nftables (modern), and friendly wrappers like ufw and firewalld.
The simple case: ufw (Uncomplicated Firewall)
ufw is the default firewall tool on Ubuntu and a popular choice on Debian. It hides nftables/iptables behind one-liner commands.
# Install (Ubuntu has it preinstalled)
sudo apt install ufw
# Set sane defaults: deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow specific services
sudo ufw allow ssh # port 22
sudo ufw allow http # port 80
sudo ufw allow https # port 443
sudo ufw allow 8080/tcp
sudo ufw allow 53/udp
# Allow from a specific IP only
sudo ufw allow from 198.51.100.5 to any port 22
# Allow a range
sudo ufw allow 60000:61000/udp
# Deny something explicitly
sudo ufw deny 23 # block telnet
# Enable the firewall (this drops everything not allowed)
sudo ufw enable
# Status
sudo ufw status verbose
sudo ufw status numbered # lists rules with numbers (for delete)
# Delete a rule by number
sudo ufw delete 3
# Reset all rules
sudo ufw reset
Critical: open SSH BEFORE enabling
If you SSH in and run ufw enable without first allowing SSH, you’ll lock yourself out. Always:
sudo ufw allow ssh
sudo ufw enable
The capable case: nftables
nftables is the modern netfilter user-space tool. Replaces iptables. More expressive syntax, less duplication.
Config lives in /etc/nftables.conf. A minimal good config:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept # localhost
ct state established,related accept # ongoing connections
ct state invalid drop
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
tcp dport ssh accept # SSH
tcp dport { http, https } accept # web
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Apply and enable:
sudo nft -f /etc/nftables.conf
sudo systemctl enable --now nftables
# View current ruleset
sudo nft list ruleset
The legacy case: iptables
iptables is being phased out but still works on most distros. Quick reference:
# Show rules
sudo iptables -L -v -n
# Allow SSH from a specific IP
sudo iptables -A INPUT -p tcp -s 198.51.100.5 --dport 22 -j ACCEPT
# Drop everything else not explicitly allowed
sudo iptables -P INPUT DROP
# Save (distro-specific)
sudo apt install iptables-persistent # then it auto-saves on reboot
sudo iptables-save > /etc/iptables/rules.v4
The “almost-only” rules you need on a typical server
- Allow SSH (preferably restricted to your IP or VPN).
- Allow ports your services use (80/443 for web, custom ports for apps).
- Allow loopback (127.0.0.1).
- Allow established/related connections.
- Drop everything else.
Test from the outside
Don’t trust your firewall config until you test it from another machine:
# From another host
nmap -p 22,80,443 myserver.com
nc -zv myserver.com 80 # connect, then close
curl -I http://myserver.com/
fail2ban: dynamic blocking
A firewall blocks ports. fail2ban blocks IPs based on suspicious behavior in your logs (failed SSH attempts, etc.):
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
# See banned IPs
sudo fail2ban-client status sshd
Common mistakes
- Enabling a firewall without allowing SSH first — instant lockout.
- Allowing 0.0.0.0/0 when you meant a specific IP — exposes to the entire internet.
- Forgetting that cloud providers ALSO have firewalls (security groups). You may need to allow at both layers.
- Not testing rules from outside — looks fine on the server, blocked from the internet.
What to learn next
That covers the networking section. Next: shell programming — turning your one-liners into reusable scripts.