Firewall Configuration

Linux firewall configuration and rule management.

firewalld Basics (RHEL/Fedora/Rocky)

# Service management
systemctl status firewalld
systemctl enable --now firewalld
systemctl restart firewalld

# Zone information
firewall-cmd --get-active-zones                     # Active zones + interfaces
firewall-cmd --get-default-zone                     # Default zone
firewall-cmd --list-all                             # Current zone rules
firewall-cmd --list-all-zones                       # All zones
firewall-cmd --zone=public --list-all               # Specific zone

# Zone assignment
firewall-cmd --get-zone-of-interface=eth0           # Interface's zone
firewall-cmd --zone=public --change-interface=eth0  # Move interface to zone

# Available services and ports
firewall-cmd --get-services                         # All predefined services
firewall-cmd --info-service=http                    # Service definition
firewall-cmd --list-ports                           # Custom ports
firewall-cmd --list-services                        # Enabled services

# Runtime vs permanent
# Runtime: immediate, lost on reload/reboot
# Permanent: survives reload/reboot, needs reload to apply
firewall-cmd --add-port=8080/tcp                    # Runtime only
firewall-cmd --add-port=8080/tcp --permanent        # Permanent (needs reload)
firewall-cmd --reload                               # Apply permanent rules

# Common zones
# drop     - Drop all incoming, no reply
# block    - Reject all incoming with icmp-host-prohibited
# public   - Default, untrusted networks
# external - NAT masquerading, external networks
# dmz      - Limited access for DMZ
# work     - Trusted work networks
# home     - Trusted home networks
# internal - Internal networks
# trusted  - Accept all traffic

firewalld Rules

# Add services
firewall-cmd --add-service=ssh --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --remove-service=cockpit --permanent   # Remove service

# Add ports
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --add-port=5000-5100/tcp --permanent   # Port range
firewall-cmd --add-port=53/udp --permanent          # UDP
firewall-cmd --remove-port=8080/tcp --permanent     # Remove port

# NodePort range for k3s
firewall-cmd --add-port=30000-32767/tcp --permanent
firewall-cmd --reload

# Protocol-based
firewall-cmd --add-protocol=icmp --permanent

# Source-based rules (rich rules)
# Allow from specific IP
firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.50.1.0/24" accept' --permanent

# Allow service from specific IP
firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.50.1.20" service name="ssh" accept' --permanent

# Drop from specific IP
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.100" drop' --permanent

# Port forward (redirect 80 to 8080)
firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080 --permanent

# Port forward to different host
firewall-cmd --add-forward-port=port=80:proto=tcp:toport=80:toaddr=10.50.1.100 --permanent

# Masquerading (NAT)
firewall-cmd --add-masquerade --permanent

# Always reload after permanent changes
firewall-cmd --reload

firewalld Rich Rules

# Rich rule syntax allows complex rules
# Format: rule [family] [source] [destination] [element] [log] [audit] [action]

# Allow SSH from management subnet only
firewall-cmd --add-rich-rule='
    rule family="ipv4"
    source address="10.50.1.0/24"
    port port="22" protocol="tcp"
    accept' --permanent

# Rate limit SSH (anti-brute force)
firewall-cmd --add-rich-rule='
    rule family="ipv4"
    service name="ssh"
    limit value="10/m"
    accept' --permanent

# Log and accept
firewall-cmd --add-rich-rule='
    rule family="ipv4"
    source address="10.50.1.20"
    service name="https"
    log prefix="ISE-HTTPS: " level="info"
    accept' --permanent

# Time-based rule (office hours only)
firewall-cmd --add-rich-rule='
    rule family="ipv4"
    source address="10.50.1.0/24"
    service name="ssh"
    accept' --permanent

# Reject with specific ICMP type
firewall-cmd --add-rich-rule='
    rule family="ipv4"
    source address="192.168.0.0/16"
    reject reject-type="icmp-admin-prohibited"' --permanent

# List rich rules
firewall-cmd --list-rich-rules

# Remove rich rule (must match exactly)
firewall-cmd --remove-rich-rule='rule family="ipv4" source address="10.50.1.0/24" accept' --permanent

Custom Zones and Services

# Create custom zone
firewall-cmd --permanent --new-zone=management
firewall-cmd --permanent --zone=management --set-description="Management network zone"
firewall-cmd --permanent --zone=management --add-source=10.50.1.0/24
firewall-cmd --permanent --zone=management --add-service=ssh
firewall-cmd --permanent --zone=management --add-service=https
firewall-cmd --reload

# Create custom service
firewall-cmd --permanent --new-service=vault
firewall-cmd --permanent --service=vault --set-description="HashiCorp Vault"
firewall-cmd --permanent --service=vault --add-port=8200/tcp
firewall-cmd --permanent --service=vault --add-port=8201/tcp  # Raft
firewall-cmd --reload

# Custom service for ISE ERS
firewall-cmd --permanent --new-service=ise-ers
firewall-cmd --permanent --service=ise-ers --set-description="Cisco ISE ERS API"
firewall-cmd --permanent --service=ise-ers --add-port=9060/tcp
firewall-cmd --reload
firewall-cmd --add-service=ise-ers --permanent

# Custom service for RADIUS
firewall-cmd --permanent --new-service=radius-full
firewall-cmd --permanent --service=radius-full --set-description="RADIUS Auth and Accounting"
firewall-cmd --permanent --service=radius-full --add-port=1812/udp
firewall-cmd --permanent --service=radius-full --add-port=1813/udp
firewall-cmd --reload

# Add service to zone
firewall-cmd --zone=trusted --add-service=vault --permanent
firewall-cmd --reload

# Delete custom service/zone
firewall-cmd --permanent --delete-service=myservice
firewall-cmd --permanent --delete-zone=myzone
firewall-cmd --reload

nftables Basics (Modern Replacement for iptables)

# nftables is the modern replacement for iptables
# Used directly by firewalld backend (RHEL 8+)

# List all rules
nft list ruleset
nft list ruleset -a                                  # With handles (for deletion)

# List specific table
nft list table inet filter
nft list chain inet filter input

# Flush all rules (dangerous!)
nft flush ruleset

# Create table and chain
nft add table inet myfilter
nft add chain inet myfilter input { type filter hook input priority 0 \; policy accept \; }

# Add rules
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport 80 accept
nft add rule inet filter input tcp dport 443 accept

# Add rule with position
nft insert rule inet filter input tcp dport 8080 accept  # First position
nft add rule inet filter input position 5 tcp dport 9000 accept  # After handle 5

# Delete rule by handle
nft -a list chain inet filter input                  # Get handles
nft delete rule inet filter input handle 7

# Counter and log
nft add rule inet filter input tcp dport 22 counter accept
nft add rule inet filter input tcp dport 22 log prefix \"SSH: \" accept

# Save and restore
nft list ruleset > /etc/nftables.conf
nft -f /etc/nftables.conf

# Service management
systemctl enable --now nftables

nftables Advanced Rules

# Sets (efficient IP/port lists)
nft add set inet filter allowed_ips { type ipv4_addr \; }
nft add element inet filter allowed_ips { 10.50.1.20, 10.50.1.60, 10.50.1.90 }
nft add rule inet filter input ip saddr @allowed_ips accept

# Named set for ports
nft add set inet filter web_ports { type inet_service \; }
nft add element inet filter web_ports { 80, 443, 8080 }
nft add rule inet filter input tcp dport @web_ports accept

# Maps (key-value pairs)
nft add map inet filter port_redirect { type inet_service : inet_service \; }
nft add element inet filter port_redirect { 80 : 8080, 443 : 8443 }

# Rate limiting
nft add rule inet filter input tcp dport 22 limit rate 5/minute accept
nft add rule inet filter input tcp dport 22 limit rate over 5/minute drop

# Connection tracking
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ct state invalid drop

# NAT table
nft add table ip nat
nft add chain ip nat prerouting { type nat hook prerouting priority 0 \; }
nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }

# DNAT (port forward)
nft add rule ip nat prerouting tcp dport 80 dnat to 10.50.1.100:8080

# SNAT/Masquerade
nft add rule ip nat postrouting oifname eth0 masquerade

# Complete example: infrastructure firewall
nft -f - <<'EOF'
table inet infrastructure {
    set mgmt_hosts {
        type ipv4_addr
        elements = { 10.50.1.20, 10.50.1.60, 10.50.1.90, 10.50.1.99 }
    }

    set admin_ports {
        type inet_service
        elements = { 22, 8200, 9060 }
    }

    chain input {
        type filter hook input priority 0; policy drop;

        ct state established,related accept
        ct state invalid drop

        iif lo accept

        ip saddr @mgmt_hosts tcp dport @admin_ports accept

        tcp dport { 80, 443 } accept

        icmp type echo-request limit rate 5/second accept

        log prefix "DROPPED: " counter drop
    }
}
EOF

iptables Legacy (Reference)

# iptables is being replaced by nftables but still common
# Use for older systems or when required

# List rules
iptables -L -n -v                                    # All chains
iptables -L INPUT -n -v --line-numbers               # Specific chain with numbers
iptables -S                                          # Print rules as commands
iptables-save                                        # Full ruleset (save format)

# Flush rules (dangerous!)
iptables -F                                          # Flush all
iptables -F INPUT                                    # Flush specific chain
iptables -X                                          # Delete custom chains

# Default policies
iptables -P INPUT DROP                               # Default deny (careful!)
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Basic rules
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Source filtering
iptables -A INPUT -s 10.50.1.0/24 -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -s 192.168.1.100 -j DROP           # Block specific IP

# Rate limiting
iptables -A INPUT -p tcp --dport 22 -m limit --limit 5/min -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

# Logging
iptables -A INPUT -j LOG --log-prefix "DROPPED: "
iptables -A INPUT -j DROP

# Insert rule (at position)
iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT

# Delete rule
iptables -D INPUT -p tcp --dport 8080 -j ACCEPT      # By specification
iptables -D INPUT 5                                  # By line number

# NAT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.50.1.100:8080

# Save and restore
iptables-save > /etc/iptables.rules
iptables-restore < /etc/iptables.rules

# Persistent on RHEL/CentOS
service iptables save

UFW (Ubuntu/Debian)

# UFW is a user-friendly frontend for iptables
# Default on Ubuntu

# Enable/disable
ufw enable
ufw disable
ufw status verbose

# Default policies
ufw default deny incoming
ufw default allow outgoing

# Allow services
ufw allow ssh                                        # By service name
ufw allow 22/tcp                                     # By port
ufw allow 80,443/tcp                                 # Multiple ports
ufw allow 5000:5100/tcp                              # Port range

# Allow from specific source
ufw allow from 10.50.1.0/24
ufw allow from 10.50.1.20 to any port 22
ufw allow from 10.50.1.0/24 to any port 22 proto tcp

# Deny rules
ufw deny 23/tcp                                      # Block telnet
ufw deny from 192.168.1.100

# Delete rules
ufw delete allow 80/tcp
ufw status numbered                                  # Show numbers
ufw delete 3                                         # Delete by number

# Application profiles
ufw app list                                         # Available apps
ufw app info "Apache Full"                           # App details
ufw allow "Apache Full"                              # Allow app profile

# Logging
ufw logging on
ufw logging medium

# Reset
ufw reset                                            # Remove all rules

# Rate limiting
ufw limit ssh                                        # 6 connections per 30 seconds

pfSense Management via netapi

# netapi provides CLI access to pfSense
# Load credentials first
dsource d000 dev/network

# DNS management
netapi pfsense dns list                              # List DNS overrides
netapi pfsense dns add \
    -h vault-02 \
    -d inside.domusdigitalis.dev \
    -i 10.50.1.61 \
    --descr "Vault HA node 2"
netapi pfsense dns delete vault-02.inside.domusdigitalis.dev

# Firewall aliases (IP groups)
netapi pfsense alias list
netapi pfsense alias show MGMT_HOSTS
netapi pfsense alias create MGMT_HOSTS \
    --type host \
    --descr "Management hosts" \
    --addresses "10.50.1.20,10.50.1.60,10.50.1.90"
netapi pfsense alias update MGMT_HOSTS \
    --addresses "10.50.1.20,10.50.1.60,10.50.1.90,10.50.1.99"

# Note: Firewall rules require API access
# Use pfSense WebUI for complex rules

# Status checks
netapi pfsense status                                # System status
netapi pfsense interfaces                            # Interface status

# DHCP leases
netapi pfsense dhcp leases                           # Current leases

# ARP table
netapi pfsense arp                                   # ARP entries

Firewall for Kubernetes/k3s

# k3s requires specific ports open
# Control plane ports
firewall-cmd --add-port=6443/tcp --permanent         # API server
firewall-cmd --add-port=2379-2380/tcp --permanent    # etcd (if external)
firewall-cmd --add-port=10250/tcp --permanent        # Kubelet API
firewall-cmd --add-port=10251/tcp --permanent        # kube-scheduler
firewall-cmd --add-port=10252/tcp --permanent        # kube-controller-manager

# Cilium CNI ports
firewall-cmd --add-port=4240/tcp --permanent         # Cilium health
firewall-cmd --add-port=4244/tcp --permanent         # Hubble relay

# NodePort range
firewall-cmd --add-port=30000-32767/tcp --permanent

# Flannel (if used)
firewall-cmd --add-port=8472/udp --permanent         # VXLAN

# MetalLB
firewall-cmd --add-port=7946/tcp --permanent         # memberlist
firewall-cmd --add-port=7946/udp --permanent

# Apply changes
firewall-cmd --reload

# Alternatively, use trusted zone for cluster network
firewall-cmd --zone=trusted --add-source=10.50.1.0/24 --permanent
firewall-cmd --reload

# Verify
firewall-cmd --list-all

# For development (NOT production): disable firewall
# systemctl stop firewalld
# systemctl disable firewalld

Firewall Troubleshooting

# Check if firewall is blocking
# 1. Temporarily disable and test
systemctl stop firewalld
# Test connectivity
systemctl start firewalld

# 2. Check active rules
firewall-cmd --list-all
iptables -L -n -v | grep DROP

# 3. Monitor rejected packets
journalctl -u firewalld -f                           # firewalld logs
dmesg | grep -i iptables                             # Kernel messages
dmesg | grep -i nf_                                  # Netfilter messages

# 4. Add logging rule
firewall-cmd --add-rich-rule='rule family="ipv4" log prefix="FIREWALL: " level="info"' --permanent
firewall-cmd --reload
journalctl -f | grep FIREWALL

# 5. Trace specific port
# Watch for connections to port 8080
iptables -I INPUT -p tcp --dport 8080 -j LOG --log-prefix "DEBUG-8080: "
# Test, then remove
iptables -D INPUT -p tcp --dport 8080 -j LOG --log-prefix "DEBUG-8080: "

# 6. Connection tracking
conntrack -L                                         # All tracked connections
conntrack -L -p tcp --dport 22                       # SSH connections
conntrack -E                                         # Events (realtime)

# 7. Verify service is actually listening
ss -tlnp | grep :8080                                # Is something bound?

# 8. Test from remote
nc -zv server 8080                                   # Can we reach it?
curl -v telnet://server:8080                         # Alternative test

# Common issues:
# - Service not listening (ss -tlnp shows nothing)
# - Firewall blocking (iptables -L shows DROP)
# - SELinux blocking (ausearch -m avc -ts recent)
# - Wrong interface/zone (firewall-cmd --get-zone-of-interface)

Infrastructure Firewall Patterns

# Vault server firewall
firewall-cmd --new-zone=vault-server --permanent
firewall-cmd --zone=vault-server --add-port=8200/tcp --permanent   # API
firewall-cmd --zone=vault-server --add-port=8201/tcp --permanent   # Raft
firewall-cmd --zone=vault-server --add-source=10.50.1.0/24 --permanent
firewall-cmd --zone=vault-server --add-interface=eth0 --permanent
firewall-cmd --reload

# ISE PSN firewall
RADIUS_PORTS="1812/udp 1813/udp 1645/udp 1646/udp"
for port in $RADIUS_PORTS; do
    firewall-cmd --add-port=$port --permanent
done
firewall-cmd --add-port=9060/tcp --permanent         # ERS API
firewall-cmd --add-port=443/tcp --permanent          # WebUI
firewall-cmd --reload

# DNS server firewall
firewall-cmd --add-service=dns --permanent           # 53/tcp+udp
firewall-cmd --add-port=953/tcp --permanent          # rndc
firewall-cmd --reload

# KVM host firewall
firewall-cmd --add-service=libvirt --permanent
firewall-cmd --add-service=libvirt-tls --permanent
firewall-cmd --add-service=vnc-server --permanent
firewall-cmd --reload

# Multi-host firewall check
HOSTS="vault-01 ise-01 bind-01 kvm-01"
for host in $HOSTS; do
    echo "=== $host ==="
    ssh "$host" "firewall-cmd --list-all" 2>/dev/null | grep -E 'ports:|services:'
done

Common Gotchas

# WRONG: Adding port without --permanent (lost on reload)
firewall-cmd --add-port=8080/tcp
# ... time passes, someone runs firewall-cmd --reload
# Port rule is GONE

# CORRECT: Always use --permanent, then reload
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload

# WRONG: Forgetting reload after permanent changes
firewall-cmd --add-port=8080/tcp --permanent
# Rule exists in config but NOT active!

# CORRECT: Reload to apply
firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload

# WRONG: Setting INPUT DROP before allowing SSH
iptables -P INPUT DROP                               # Locked out!

# CORRECT: Add SSH rule FIRST
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP

# WRONG: Not preserving established connections
iptables -P INPUT DROP                               # Kills existing SSH

# CORRECT: Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP

# WRONG: Assuming firewall = security
# Firewall allows SSH doesn't mean SSH is secure

# CORRECT: Defense in depth
# - Firewall restricts access
# - SSH uses key auth, not passwords
# - fail2ban blocks brute force
# - SELinux/AppArmor confines processes

# WRONG: Opening all ports for k8s
firewall-cmd --zone=trusted --add-source=0.0.0.0/0

# CORRECT: Only trust cluster network
firewall-cmd --zone=trusted --add-source=10.50.1.0/24

# WRONG: Forgetting IPv6
firewall-cmd --add-port=22/tcp --permanent           # IPv4 only?

# CORRECT: firewalld handles both by default
# nftables inet family covers both

Quick Reference

# firewalld
firewall-cmd --list-all                   # Show all rules
firewall-cmd --add-port=X/tcp --permanent # Add port
firewall-cmd --add-service=X --permanent  # Add service
firewall-cmd --reload                     # Apply changes
firewall-cmd --get-active-zones           # Active zones

# nftables
nft list ruleset                          # All rules
nft list ruleset -a                       # With handles
nft add rule inet filter input tcp dport X accept
nft delete rule inet filter input handle N

# iptables
iptables -L -n -v                         # List rules
iptables -A INPUT -p tcp --dport X -j ACCEPT
iptables-save > /etc/iptables.rules       # Save
iptables-restore < /etc/iptables.rules    # Restore

# UFW
ufw status verbose                        # Status
ufw allow 22/tcp                          # Allow port
ufw deny from IP                          # Block IP
ufw enable                                # Enable

# Common ports to allow
# 22    SSH
# 80    HTTP
# 443   HTTPS
# 53    DNS (tcp+udp)
# 123   NTP
# 8200  Vault
# 6443  k8s API
# 9060  ISE ERS
# 1812  RADIUS

# Troubleshooting
ss -tlnp | grep PORT                      # Is it listening?
nc -zv host PORT                          # Can we connect?
journalctl -u firewalld -f                # Watch logs
conntrack -L                              # Connection tracking