Firewall
Firewall rule management across iptables, nftables, ufw, and firewalld.
firewalld — Zone-Based Firewall
firewalld uses zones to define trust levels. Each network interface is assigned to exactly one zone. Traffic entering that interface is subject to that zone’s rules.
Zone Management
sudo firewall-cmd --list-all-zones
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-all
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --set-default-zone=drop
Common Zones
| Zone | Default Behavior | Use Case |
|---|---|---|
drop |
Drop all incoming, no reply |
Untrusted networks, internet-facing |
block |
Reject all incoming (ICMP reject) |
Similar to drop but polite |
public |
Reject incoming except allowed |
Default — servers with selective access |
internal |
More permissive than public |
Trusted LAN segments |
trusted |
Accept everything |
Loopback only — never assign a real interface |
Adding Services and Ports
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-port=8443/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-port={80,443,8080}/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --remove-service=cockpit --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --get-services
The --permanent flag writes to config. Without it, the rule is runtime-only and lost on reload. Always use --permanent + --reload together.
Rich Rules — Granular Control
Rich rules are firewalld’s answer to complex iptables rules. They combine source, destination, port, and action.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.50.1.0/24" service name="ssh" accept' --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-rich-rule='rule service name="ssh" accept limit value="3/m"' --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" log prefix="BLOCKED: " level="warning" drop' --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.50.1.20" port port="1812-1813" protocol="udp" accept' --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-rich-rules
ufw — Uncomplicated Firewall
ufw is the Ubuntu/Debian frontend for iptables. Simpler than firewalld but less flexible.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow from 10.50.1.0/24 to any port 22
sudo ufw allow 8000:8100/tcp
sudo ufw deny from 192.168.1.100
sudo ufw status numbered
sudo ufw delete 3
sudo ufw status verbose
nftables — The Modern Backend
firewalld and ufw are frontends. nftables is the actual kernel framework (replacing iptables). Direct nftables rules are for when you need performance or features the frontends don’t expose.
sudo nft list ruleset
sudo nft list table inet filter
sudo nft flush ruleset
Hardened Server Baseline
A minimal firewall for a server that only runs SSH + a web app.
# Set default to drop — deny everything first
sudo firewall-cmd --set-default-zone=drop
# Allow SSH from management subnet only
sudo firewall-cmd --zone=drop --add-rich-rule='rule family="ipv4" source address="10.50.1.0/24" service name="ssh" accept' --permanent
# Allow HTTPS from anywhere
sudo firewall-cmd --zone=drop --add-port=443/tcp --permanent
# Allow ICMP ping from local net (monitoring)
sudo firewall-cmd --zone=drop --add-rich-rule='rule family="ipv4" source address="10.50.0.0/16" protocol value="icmp" accept' --permanent
# Apply
sudo firewall-cmd --reload
sudo firewall-cmd --zone=drop --list-all
Defense in Depth — Layered Rules
Firewall rules are one layer. A hardened server combines multiple layers:
| Layer | Tool | What It Controls |
|---|---|---|
Network firewall |
pfSense, Palo Alto |
Inter-VLAN, internet egress |
Host firewall |
firewalld, ufw |
Per-server ingress/egress |
Application |
nginx, Apache |
TLS, request filtering, rate limiting |
Authentication |
SSH keys, Vault |
Who can access |
Monitoring |
Wazuh, journald |
Detect and alert on anomalies |
sudo grep -c "firewalld" /var/ossec/logs/alerts/alerts.json
Port Knocking — Concept
Port knocking hides services behind a sequence of connection attempts. The firewall only opens the real port after seeing the correct knock sequence.
1. Client sends SYN to port 7000 2. Client sends SYN to port 8000 3. Client sends SYN to port 9000 4. Firewall opens port 22 for that source IP for 30 seconds 5. Client connects to SSH
Port knocking is security through obscurity. It adds a layer but should never be the only defense. Prefer VPN or Vault-signed SSH certificates instead.
Troubleshooting
nc -zv target-host 443
ss -tlnp
sudo journalctl -k | grep -i "DROPPED\|REJECTED\|BLOCK" | tail -20
sudo journalctl -u firewalld --since "1 hour ago"
# Before
sudo firewall-cmd --zone=public --list-all
# Change
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
# After
sudo firewall-cmd --zone=public --list-all