Skip to content

Firewalls

A firewall is a network security system that monitors and controls all network traffic. It applies rules on both incoming and outgoing connections, building flexible barriers depending on the level of trust and network topology of a given connection.

Firewalls can be hardware or software based - found in routers, individual computers, or network nodes. Many also have routing capabilities.

  • They protect against intrusions and other attack vectors
  • They control the trust level on particular interfaces and/or source addresses

Almost all firewalls are based on packet filtering. Information travels across networks in packets, each containing:

  • Header - source/destination addresses, protocol, flags, sequence numbers
  • Payload - the actual data
  • Footer - error checking

Packet filtering intercepts packets at one or more stages in the network transmission stack (application, transport, network, datalink). A firewall establishes a set of rules by which each packet may be:

  • Accepted or rejected based on content, address, or protocol
  • Mangled (modified) in some way
  • Redirected to another address
  • Inspected for security purposes
  • 1st gen - Packet filtering (late 1980s): inspects individual packets in isolation; no awareness of connection state. Stateless.
  • 2nd gen - Stateful filters: tracks connection state (NEW, ESTABLISHED, RELATED, INVALID). Can detect and block attacks that exploit connection assumptions.
  • 3rd gen - Application layer firewalls: understands application protocols (HTTP, FTP, DNS). Can block traffic that doesn’t conform to expected protocol behavior even on allowed ports.
ToolLayerWhen to use
nftablesKernel (netfilter)Modern replacement for iptables; use for new raw setups
iptablesKernel (netfilter)Legacy; still widely documented; translates to nftables on modern kernels
firewalldService (wraps nftables/iptables)RHEL/Fedora/CentOS - zone-based, persistent, most practical
ufwFrontend (wraps iptables)Debian/Ubuntu - simpler syntax, good for single-host rules

firewalld is the Dynamic Firewall Manager. It uses network/firewall zones - defined trust levels for interfaces or source addresses - and separates runtime from permanent (persistent) configuration.

Configuration files live in two locations:

  • /usr/lib/firewalld/ - system defaults (don’t edit)
  • /etc/firewalld/ - admin customizations (these override the above)
Terminal window
sudo systemctl enable --now firewalld
sudo systemctl status firewalld
sudo firewall-cmd --state # quick check: "running" or "not running"
# IP forwarding (required when acting as a router)
sudo sysctl net.ipv4.ip_forward=1
# Persistent:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-forward.conf
sudo sysctl -p /etc/sysctl.d/99-forward.conf

Each interface belongs to a zone. NetworkManager assigns zones automatically, but you can override them.

ZoneBehavior
trustedAll connections allowed
homeSelected inbound allowed; hosts trusted
workSelected inbound allowed; nodes not fully trusted
publicDefault; only explicitly allowed services permitted
dmzSelected services exposed; rest blocked
externalMasquerading (NAT) enabled; used for router-facing interfaces
blockInbound rejected with ICMP-reject; own connections allowed
dropInbound silently dropped; no reply sent

On installation, most distributions set public as the default zone.

Terminal window
# Inspect
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones # interfaces → which zone
sudo firewall-cmd --get-zones # list all available zones
sudo firewall-cmd --get-zone-of-interface=eno1
# Inspect a zone in full detail
sudo firewall-cmd --zone=public --list-all
# Change zone
sudo firewall-cmd --set-default-zone=public
sudo firewall-cmd --zone=internal --change-interface=eno1 # runtime
sudo firewall-cmd --permanent --zone=internal --change-interface=eno1 # persistent
# Bind a source subnet to a zone
sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
sudo firewall-cmd --permanent --zone=trusted --list-sources

firewalld services are named port/protocol combos defined in /usr/lib/firewalld/services/.

Terminal window
# Services
sudo firewall-cmd --get-services
sudo firewall-cmd --list-services --zone=public
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --permanent --zone=public --remove-service=http
sudo firewall-cmd --reload # apply permanent rules to runtime
# Ports (when no service name exists)
sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
sudo firewall-cmd --permanent --zone=public --add-port=5000-5010/tcp
sudo firewall-cmd --zone=public --list-ports
# Rich rules (complex matching: IP + port + action)
sudo firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family=ipv4 source address="10.0.0.0/8" port port=22 protocol=tcp accept'
sudo firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family=ipv4 source address="1.2.3.4" drop'
sudo firewall-cmd --reload
Terminal window
# Forward port 80 → 8080 (same host)
sudo firewall-cmd --permanent --zone=external \
--add-forward-port=port=80:proto=tcp:toport=8080
# Forward port 80 → different host
sudo firewall-cmd --permanent --zone=external \
--add-forward-port=port=80:proto=tcp:toport=80:toaddr=192.168.1.20
sudo firewall-cmd --zone=external --list-all
Terminal window
sudo firewall-cmd --permanent --zone=external --add-masquerade
sudo firewall-cmd --reload

Terminal window
# Enable / status
sudo ufw enable
sudo ufw status verbose
sudo ufw status numbered # numbered rules - useful for deletion
# Allow / deny
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 5000:5010/tcp
sudo ufw deny 23/tcp
# Source-specific rules
sudo ufw allow from 192.168.1.0/24
sudo ufw allow from 192.168.1.100 to any port 22
# Delete
sudo ufw delete allow 80/tcp
sudo ufw delete 3 # by number from 'status numbered'
# Logging
sudo ufw logging on

NAT is how a firewall changes the source or destination IP (and optionally port) of packets as they pass through. There are two main types:

DNAT - Destination NAT (inbound / port forwarding)

Section titled “DNAT - Destination NAT (inbound / port forwarding)”

DNAT changes the destination IP of incoming packets - used to expose an internal server to the internet.

DNAT diagram

Flow example:

  1. SystemA sends a packet to the firewall’s public IP on port 80 (http)
  2. DNAT rule rewrites the destination IP → internal web server at 192.168.2.5:80
  3. The internal server processes and replies; the firewall translates the reply back

DNAT rules live in the PREROUTING chain (before the routing decision).

  • The DNAT firewall rule inspects: destination IP (must match the firewall’s public IP) and destination port
  • The rule changes the destination IP to the internal server’s address
  • Return traffic is automatically translated back

SNAT changes the source IP of outgoing packets - used when internal hosts need internet access through a single public IP.

SNAT diagram

  • Translation is required because internal private addresses aren’t routable on the internet
  • Masquerade = dynamic SNAT (for DHCP/dynamic public IPs) - more compute overhead
  • SNAT = static SNAT with a fixed public IP - more efficient

SNAT/Masquerade rules live in the POSTROUTING chain (the last point before the packet leaves).


Still widely used in scripts and environments without firewalld.

Terminal window
# View rules
sudo iptables -L -v -n # list all chains with counters
sudo iptables -L INPUT -v -n # INPUT chain specifically
sudo iptables -t nat -L -v -n # NAT table
# Allow / deny
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -P INPUT DROP # set default policy to drop
# NAT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT \
--to-destination 192.168.1.10:80
# Persist (Debian/Ubuntu)
sudo apt install iptables-persistent && sudo netfilter-persistent save
# Persist (RHEL/Fedora)
sudo service iptables save