Perimeter Shield -- Linux Firewalls

Slide 1 of 40  |  Week 2  |  Advanced Linux Administration
Perimeter Shield
Linux Firewalls
iptables  •  nftables  •  UFW  •  Zone Policies  •  Default Deny
Operational directive: deploy your cell's firewall before the next grid sync. Your node is exposed on a public interface. Every packet entering without explicit authorization is a potential intrusion vector. This session gives you the tools to lock it down.
40 Slides Week 2 Topic 1 Ubuntu 22.04 iptables / nftables / UFW
Slide 2 of 40
Why Firewalls?
A firewall is the mandatory first line of defense for any Linux system on a network.
What a Firewall Does
Inspects every network packet arriving at or leaving your system. Applies a ruleset to decide: accept, drop, or reject. Operates at the kernel level -- no userspace overhead for the packet decision path.
What It Does Not Do
A firewall is not an IDS, not an antivirus, not an application proxy. It cannot inspect encrypted payload contents. It cannot stop a vulnerability in an application you chose to expose. It controls access -- defense in depth handles the rest.
The Linux Stack
The kernel's netfilter framework is the core. iptables and nftables are userspace frontends to netfilter. UFW is a frontend to iptables. All roads lead to netfilter hooks in the kernel network stack.
UFW wraps iptables / nftables configures netfilter (kernel) Frontend Userspace tools Kernel engine All roads lead to netfilter hooks in the kernel
Ubuntu 22.04 Default State
Fresh Ubuntu 22.04 installs ship with UFW installed but disabled by default. The kernel has no active rules. Every port your services bind is reachable until you enable and configure the firewall. Never assume you are protected by default.
Three Tool Layers
netfilter -- kernel hooks, the actual enforcement engine.
iptables / nftables -- direct rule management, full power.
UFW -- simplified syntax wrapping iptables, good for common cases.
Slide 3 of 40
Netfilter: The Kernel Engine
Understanding the layer beneath iptables before writing a single rule.
Every packet that enters or leaves your Linux system passes through a series of hooks in the kernel's network stack. Netfilter places your rules at these hooks. iptables and nftables are just the configuration interfaces -- the actual work happens in kernel space.
NIC PREROUTING R INPUT Local Process FORWARD OUTPUT POSTROUTING NIC for this host routed through
Hook Points
PREROUTING -- packet arrives, before routing decision. INPUT -- packet destined for this machine. FORWARD -- packet routed through this machine. OUTPUT -- packet originating from this machine. POSTROUTING -- after routing decision, before leaving.
Packet Flow: Incoming
NIC receives packet. Kernel checks PREROUTING. Routing decision: is this for me or to be forwarded? If for local process: INPUT chain. If to be forwarded: FORWARD chain then POSTROUTING.
Packet Flow: Outgoing
Local process generates packet. Kernel applies OUTPUT chain rules. Routing decision. POSTROUTING chain. Packet leaves via NIC. All outbound traffic can be controlled before it departs.
# See what netfilter modules are loaded $ lsmod | grep nf_ nf_conntrack 180224 3 xt_conntrack,nf_nat,nf_conntrack_netlink nf_tables 278528 0 nf_nat 57344 1 xt_MASQUERADE # Check kernel version (affects available netfilter features) $ uname -r 5.15.0-91-generic
Slide 4 of 40
iptables Tables
iptables organizes rules into tables, each serving a different purpose.
filter (default)
The table you use most. Contains the three primary chains: INPUT, OUTPUT, FORWARD. When you write a firewall rule to allow or block traffic, you are working in the filter table. If you omit -t, filter is assumed.
nat
Network Address Translation. Contains PREROUTING and POSTROUTING chains. Used for port forwarding, SNAT (masquerade outbound traffic through a single IP), DNAT (redirect inbound traffic to a different destination).
mangle
Modifies packet headers. Used for Quality of Service (QoS), setting TTL, TOS bits, marking packets for policy routing. Advanced traffic shaping use case. Rarely touched in standard server hardening.
raw
Highest priority table. Processed before connection tracking. Used to exempt certain packets from conntrack for performance. Contains PREROUTING and OUTPUT chains. Rarely needed in standard configurations.
security
Used for Mandatory Access Control (MAC) networking rules -- specifically for SELinux. Sets security context marks on packets. Only relevant when SELinux is enforcing. Ubuntu uses AppArmor by default, not SELinux.
Slide 5 of 40
iptables Chains: INPUT, OUTPUT, FORWARD
The three built-in chains in the filter table. Know them cold.
INPUT
Governs all packets destined for this machine. SSH connections hitting port 22, HTTP hitting port 80, ICMP pings -- all traverse INPUT. This is where you allow or block services on your server. Most firewall rules live here.
OUTPUT
Governs all packets generated by this machine. wget downloads, outbound SSH from your server, DNS queries -- all traverse OUTPUT. Many admins leave OUTPUT as ACCEPT. Locking it down is best practice for high-security environments.
FORWARD
Governs packets being routed through this machine from one interface to another. Only relevant if your Linux box acts as a router or gateway. Requires net.ipv4.ip_forward=1 in sysctl to be effective.
INPUT chain -- rules evaluated top to bottom Rule 1: -i lo -j ACCEPT Rule 2: ESTABLISHED -j ACCEPT Rule 3: --dport 22 -j ACCEPT Policy: DROP (default) MATCH stop processing First match wins no match = policy
# List all rules in the filter table (default) $ iptables -L -v -n Chain INPUT (policy ACCEPT) target prot opt in out source destination ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0 Chain FORWARD (policy DROP) target prot opt in out source destination Chain OUTPUT (policy ACCEPT) target prot opt in out source destination
Flag Reference
-L list rules    -v verbose (show packet/byte counts)    -n numeric (no DNS lookups)    --line-numbers show rule numbers
Slide 6 of 40
Rule Syntax: Anatomy of an iptables Command
Every iptables rule follows the same structure. Internalize this pattern.
# General syntax: iptables [-t table] COMMAND chain [match criteria] -j TARGET # Commands: -A append, -I insert, -D delete, -R replace, -F flush, -P policy # Targets: ACCEPT, DROP, REJECT, LOG, MASQUERADE, DNAT, SNAT # Allow inbound SSH from any source $ iptables -A INPUT -p tcp --dport 22 -j ACCEPT # Allow inbound SSH only from a specific network $ iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT # Allow established/related connections (stateful) $ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Drop everything else (must be the LAST rule) $ iptables -A INPUT -j DROP # View rules with line numbers $ iptables -L INPUT --line-numbers -n
Slide 7 of 40
Targets: ACCEPT, DROP, REJECT
Three core decisions -- and they are not interchangeable. The choice has operational consequences.
ACCEPT
The packet is allowed through. Rule processing stops -- no further rules in this chain are evaluated for this packet. The session proceeds normally. Use for all explicitly permitted traffic.
DROP
The packet is silently discarded. No response is sent to the source. The sender's connection times out waiting for a reply. This is the stealth option. Preferred for public-facing servers -- reveals nothing about what is running.
REJECT
The packet is discarded and an ICMP error (port unreachable) or TCP RST is sent back. The sender knows immediately that the connection was refused. Use on internal networks where fast failure feedback is useful. Never use on public interfaces.
Packet ACCEPT packet passes through DROP X silent discard -- sender times out REJECT X ICMP error sent back to source DROP = stealth (preferred)
# DROP -- silent. Source sees a timeout. $ iptables -A INPUT -p tcp --dport 8080 -j DROP # REJECT with an explicit ICMP message (host unreachable) $ iptables -A INPUT -p tcp --dport 8080 -j REJECT --reject-with icmp-host-unreachable # REJECT with TCP reset (useful for internal services) $ iptables -A INPUT -p tcp --dport 8080 -j REJECT --reject-with tcp-reset
Security Note
On public interfaces, always use DROP. REJECT tells attackers your firewall is active and the port is filtered -- useful information for reconnaissance. DROP gives away nothing.
Slide 8 of 40
Stateful Matching: conntrack
Track connection state to allow reply traffic without opening the port permanently.
You want your server to initiate outbound connections (apt updates, API calls) and receive the replies, but you do not want random inbound connections on those ports. Connection tracking solves this: accept the reply if WE initiated the session.
NEW SYN sent SYN-ACK ESTABLISHED ongoing session spawns RELATED e.g. FTP data INVALID drop these conntrack state machine -- iptables -m conntrack --ctstate
Connection States
NEW -- first packet of a new connection.
ESTABLISHED -- part of an ongoing session.
RELATED -- related to an established connection (e.g., FTP data channel).
INVALID -- does not match any known connection. Drop these.
Why This Matters
Without conntrack, allowing inbound traffic for outbound connections means opening all high ports (1024-65535) inbound. With conntrack, you only allow ESTABLISHED,RELATED inbound -- which means only packets replying to sessions you initiated.
# The stateful ruleset skeleton (apply this to every server) # 1. Always allow loopback traffic $ iptables -A INPUT -i lo -j ACCEPT # 2. Accept established/related connections $ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 3. Drop invalid packets before they can be processed $ iptables -A INPUT -m conntrack --ctstate INVALID -j DROP # 4. Allow new SSH connections $ iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT # 5. Drop everything else $ iptables -P INPUT DROP
Slide 9 of 40
Common Rules: Services and Ports
Real rules for the services you will actually run.
# Allow HTTP and HTTPS inbound $ iptables -A INPUT -p tcp --dport 80 -j ACCEPT $ iptables -A INPUT -p tcp --dport 443 -j ACCEPT # Allow DNS queries outbound (TCP and UDP) $ iptables -A OUTPUT -p udp --dport 53 -j ACCEPT $ iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT # Allow ICMP ping (inbound) $ iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT # Allow a specific port range (passive FTP data) $ iptables -A INPUT -p tcp --dport 60000:65000 -j ACCEPT # Block a specific IP address completely $ iptables -I INPUT 1 -s 203.0.113.42 -j DROP # Allow multiple ports with multiport extension $ iptables -A INPUT -p tcp -m multiport --dports 80,443,8080,8443 -j ACCEPT
Slide 10 of 40
Default Policies: The Foundation of Deny
The chain policy is what happens to packets that match NO rules. Set it deliberately.
Default ACCEPT 22 80 443 3306 5432 8080 ... all open Forgot to block = exposed Default DROP 22 80 X X X ... all blocked Forgot to allow = safe (service down, not breached) Default DROP is always the correct choice
Default ACCEPT (Permissive)
Out of the box policy. Every packet not explicitly blocked is allowed. You add rules to block things. The problem: if you forget to block something, it is open. One misconfigured service exposes an unintended port immediately.
Default DROP (Restrictive)
Every packet not explicitly allowed is dropped. You add rules to open things. If you forget to allow something, it is blocked. The correct approach for any server on a network. A missed rule breaks a service -- that is fixable. A missed block rule is a breach.
# CRITICAL: Do this AFTER adding your ACCEPT rules, not before. # Setting INPUT to DROP before adding SSH ACCEPT will lock you out. # Step 1: Flush existing rules and start clean $ iptables -F # flush all rules in filter table $ iptables -X # delete all custom chains # Step 2: Add your ACCEPT rules first (loopback, established, SSH, etc.) $ iptables -A INPUT -i lo -j ACCEPT $ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT $ iptables -A INPUT -p tcp --dport 22 -j ACCEPT # Step 3: NOW set the default policy to DROP $ iptables -P INPUT DROP $ iptables -P FORWARD DROP $ iptables -P OUTPUT ACCEPT
Lockout Warning
If you set INPUT to DROP before adding an SSH ACCEPT rule and you are connected via SSH, your session dies immediately. Always add your ACCEPT rules first, then set the policy.
Slide 11 of 40
Logging Rules: Visibility Into Dropped Traffic
A DROP rule is silent. A LOG rule before DROP gives you evidence.
You set default deny. Traffic is being blocked -- but by what rule? Is someone port scanning you? Are legitimate services misconfigured? Logging the drops tells you exactly what is hitting your firewall and from where.
LOG Target
LOG writes to the kernel ring buffer (viewable with dmesg) and to /var/log/kern.log or syslog. Unlike DROP, LOG does not stop rule processing -- the packet continues to the next rule. LOG then DROP is the pattern.
Log Prefix
The --log-prefix option prepends a string (max 29 chars) to every log entry. This lets you grep for firewall-specific entries in your syslog and distinguish them from kernel messages. Always use a prefix on LOG rules.
# Log then drop -- the standard pattern # Insert BEFORE the default DROP policy or final DROP rule $ iptables -A INPUT -j LOG --log-prefix "IPT-DROP-IN: " --log-level 4 $ iptables -A INPUT -j DROP # View log entries in real-time $ dmesg -w | grep "IPT-DROP-IN" $ tail -f /var/log/kern.log | grep "IPT-DROP" # Sample log output: # [12345.678] IPT-DROP-IN: IN=eth0 OUT= SRC=203.0.113.42 DST=10.0.0.5 # PROTO=TCP SPT=54321 DPT=22 WINDOW=65535 SYN # Limit logging to avoid log flooding (10 packets/min, burst 20) $ iptables -A INPUT -m limit --limit 10/min --limit-burst 20 -j LOG --log-prefix "IPT-DROP-IN: "
Slide 12 of 40
Persistence: iptables-save and iptables-restore
iptables rules are lost on reboot unless you save and restore them explicitly.
The Volatile Problem
iptables rules live in kernel memory. When you reboot, the kernel reloads fresh with zero rules -- all your work is gone. You must export the rules to a file and configure the system to restore them at boot.
iptables-persistent Package
The easiest solution on Ubuntu. Install iptables-persistent, which provides a systemd service that loads saved rules at boot. Rules saved to /etc/iptables/rules.v4 (IPv4) and /etc/iptables/rules.v6 (IPv6).
# Save current rules to a file $ iptables-save > /etc/iptables/rules.v4 $ ip6tables-save > /etc/iptables/rules.v6 # Restore rules from file (used at boot by iptables-persistent) $ iptables-restore < /etc/iptables/rules.v4 # Install iptables-persistent to handle boot restore automatically $ apt install iptables-persistent # Installer asks if you want to save current rules -- answer yes # Save current rules using the netfilter-persistent service $ netfilter-persistent save # Check that the service is enabled $ systemctl is-enabled netfilter-persistent enabled # Verify the saved file looks correct $ cat /etc/iptables/rules.v4
Slide 13 of 40
nftables: The Modern Replacement
nftables replaces iptables/ip6tables/ebtables with a single, unified framework.
Why nftables Exists
iptables has separate tools for IPv4, IPv6, ARP, and bridging. nftables handles all of them with one syntax. Cleaner rule syntax, better performance through JIT compilation, atomic rule sets (apply all-or-nothing to prevent inconsistent states).
Status on Ubuntu 22.04
Ubuntu 22.04 ships nftables alongside iptables. iptables in Ubuntu 22.04 is actually iptables-nft -- a compatibility wrapper that translates iptables commands to nftables rules. Both tools work; nftables is the future.
Key Differences from iptables
Tables and chains are user-defined -- no predefined INPUT/OUTPUT/FORWARD unless you create them. Rules use a different syntax with sets, maps, and verdict statements. Configuration lives in /etc/nftables.conf.
# Check nftables version $ nft --version nftables v1.0.2 (Lester Gooch) # List all current nftables rules $ nft list ruleset # The nftables equivalent of iptables -L -v -n $ nft list table ip filter # Check if iptables is actually iptables-nft (the compatibility shim) $ update-alternatives --display iptables iptables - auto mode link best version is /usr/sbin/iptables-nft
Slide 14 of 40
nftables Syntax and Structure
Tables, chains, and rules -- the same concepts, different syntax.
table ip filter chain input {hook input; policy drop;} iif lo accept ct state established,related accept tcp dport {80, 443} accept chain output {hook output; policy accept;} chain forward {hook forward; policy drop;} user-defined tables and chains (no predefined INPUT/OUTPUT)
# /etc/nftables.conf -- minimal stateful firewall #!/usr/sbin/nft -f # Flush all existing rules flush ruleset table ip filter { chain input { # Attach to the INPUT hook, default policy DROP type filter hook input priority 0; policy drop; # Allow loopback traffic iif lo accept # Accept established and related connections ct state established,related accept # Drop invalid packets ct state invalid drop # Allow inbound SSH tcp dport 22 ct state new accept # Allow HTTP and HTTPS tcp dport { 80, 443 } accept # Log everything else before the default drop log prefix "NFT-DROP-IN: " } chain output { type filter hook output priority 0; policy accept; } chain forward { type filter hook forward priority 0; policy drop; } } # Apply the ruleset $ nft -f /etc/nftables.conf $ systemctl enable nftables
Slide 15 of 40
UFW: Uncomplicated Firewall
A simplified frontend to iptables. Appropriate for most server hardening tasks.
What UFW Does
UFW translates human-readable commands into iptables rules. It manages both IPv4 and IPv6 rules simultaneously. It maintains profiles for common applications. Good for standard server use -- not for complex NAT or policy routing scenarios.
What UFW Does Not Do
No stateful inspection configuration (it uses conntrack by default). Limited FORWARD chain management. Cannot express complex multi-table rules. When you hit its limits, drop to raw iptables or nftables.
Default State
UFW is installed but inactive on fresh Ubuntu 22.04. Enabling it with no rules set will block everything including your SSH connection. Always add rules before enabling on a remote server.
Admin types ufw allow ssh simple syntax generates iptables -A INPUT -p tcp --dport 22 -j ACCEPT kernel Uncomplicated Firewall auto-generates IPv4 + IPv6 rules
# Check UFW status $ ufw status verbose # Enable UFW (ONLY after adding SSH allow rule) $ ufw allow ssh $ ufw enable # Disable UFW $ ufw disable # Reset to blank state (removes all rules) $ ufw reset
Slide 16 of 40
UFW: Managing Rules
Allow, deny, limit, and delete -- the four actions you use daily.
# Allow by service name (reads /etc/services) $ ufw allow ssh $ ufw allow http $ ufw allow https # Allow by port number and protocol $ ufw allow 22/tcp $ ufw allow 8080/tcp $ ufw allow 53/udp # Allow from a specific source IP to a specific port $ ufw allow from 10.0.0.50 to any port 3306 # Deny a specific port $ ufw deny 23/tcp # block telnet # Rate-limit SSH (block IPs after 6 attempts in 30 seconds) $ ufw limit ssh # Delete a rule by number (list numbered rules first) $ ufw status numbered $ ufw delete 3 # Delete a rule by specification $ ufw delete allow 8080/tcp
Slide 17 of 40
UFW: Application Profiles
Packages ship pre-built UFW profiles that open exactly the ports they need.
What Are Profiles?
UFW application profiles are files in /etc/ufw/applications.d/ that define named groups of ports for a service. Instead of remembering that Nginx needs ports 80 and 443, you use ufw allow 'Nginx Full'. Package maintainers ship these with their packages.
Nginx Profile Example
Nginx HTTP -- opens port 80. Nginx HTTPS -- opens port 443. Nginx Full -- opens both 80 and 443. Use Nginx Full for HTTPS-enabled sites, Nginx HTTP before SSL certificates are installed.
# List available application profiles $ ufw app list Available applications: Apache Apache Full Apache Secure Nginx Full Nginx HTTP Nginx HTTPS OpenSSH # Get details on a profile $ ufw app info 'Nginx Full' Profile: Nginx Full Title: Web Server (Nginx, HTTP + HTTPS) Description: Small, but very powerful and efficient web server Ports: 80,443/tcp # Apply a profile rule $ ufw allow 'Nginx Full' # Inspect what UFW generated in iptables $ iptables -L ufw-user-input -n -v
Slide 18 of 40
Zone-Based Policies
Different interfaces carry different trust levels. Apply different policies per zone.
Your server has two interfaces: eth0 on the public internet and eth1 on the internal management network. SSH should be reachable from eth1 only. Web traffic hits eth0 only. The same rules for both interfaces would be wrong -- zones let you be precise.
Server public zone eth0 -- untrusted HTTP only internal zone eth1 -- management SSH + DB trusted zone lo -- allow all all traffic
Zone Concept
A zone is a label for a trust level. Each network interface is assigned to a zone. Rules are applied per-zone. The public zone is untrusted -- minimum access. The internal zone may allow more services. The dmz zone exposes specific public services.
Interface-Specific iptables Rules
Use -i (input interface) and -o (output interface) to match rules to specific NICs. This is how you implement zones manually with iptables without requiring firewalld.
# Allow SSH only from the management interface (eth1) $ iptables -A INPUT -i eth1 -p tcp --dport 22 -j ACCEPT $ iptables -A INPUT -i eth0 -p tcp --dport 22 -j DROP # Allow web traffic only from the public interface (eth0) $ iptables -A INPUT -i eth0 -p tcp -m multiport --dports 80,443 -j ACCEPT # Allow database access only from internal interface $ iptables -A INPUT -i eth1 -p tcp --dport 3306 -s 10.0.1.0/24 -j ACCEPT
Slide 19 of 40
NAT: Port Forwarding with iptables
Redirect inbound traffic on one port to a different destination or port using DNAT.
DNAT (Port Forward) Internet :80 PREROUTING DNAT rewrite 192.168.1.10:8080 MASQUERADE (SNAT) 192.168.1.0/24 Gateway POSTROUTING src rewritten Internet (pub IP)
DNAT (Destination NAT)
Rewrites the destination IP and/or port of incoming packets. Used to forward traffic hitting your public IP on port 80 to an internal web server at 192.168.1.10:8080. Lives in the nat table, PREROUTING chain.
MASQUERADE / SNAT
Rewrites the source IP of outbound packets to your public IP. Used on gateway servers to allow internal hosts to reach the internet via a single public IP. MASQUERADE auto-detects the interface IP; SNAT requires a specific address.
# Enable IP forwarding (required for NAT / routing) $ sysctl -w net.ipv4.ip_forward=1 $ echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf # Forward port 80 on this machine to an internal server $ iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT \ --to-destination 192.168.1.10:8080 $ iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 8080 -j ACCEPT # Masquerade outbound traffic from internal network through this gateway $ iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE # Verify NAT rules $ iptables -t nat -L -v -n
Slide 20 of 40
Rate Limiting: Controlling Connection Bursts
Limit how fast new connections can arrive to throttle brute-force and DoS attempts.
# Limit new SSH connections to 3 per minute per source IP # hashlimit tracks per-source rates independently $ iptables -A INPUT -p tcp --dport 22 \ -m conntrack --ctstate NEW \ -m hashlimit \ --hashlimit-mode srcip \ --hashlimit-upto 3/min \ --hashlimit-burst 5 \ --hashlimit-name ssh-limit \ -j ACCEPT $ iptables -A INPUT -p tcp --dport 22 -j DROP # Global rate limit on ICMP to prevent ping floods $ iptables -A INPUT -p icmp --icmp-type echo-request \ -m limit --limit 1/s --limit-burst 4 -j ACCEPT $ iptables -A INPUT -p icmp -j DROP # UFW equivalent: rate-limit SSH (6 connections per 30 seconds triggers block) $ ufw limit ssh/tcp
Comparison
UFW's limit is simpler but uses a fixed threshold (6/30s). The hashlimit module gives you granular control over rate, burst, and tracking mode. Use hashlimit when you need precise tuning.
Slide 21 of 40
UFW Logging
Enable and tune UFW's built-in logging to track blocked and allowed traffic.
Log Levels
off -- no logging.
low -- blocked packets and non-standard connections.
medium -- low + new allowed connections + invalid packets.
high -- medium + all allowed connections + rate-limited.
full -- everything, including established traffic. Very verbose.
Log Location
UFW logs to /var/log/ufw.log and also to /var/log/syslog. Entries are prefixed with [UFW BLOCK] or [UFW ALLOW]. Grep for these prefixes to filter firewall events from the general system log.
# Set UFW log level $ ufw logging medium # View UFW log in real-time $ tail -f /var/log/ufw.log # Sample UFW log entries: # [UFW BLOCK] IN=eth0 OUT= SRC=203.0.113.5 DST=10.0.0.5 # PROTO=TCP SPT=54321 DPT=3306 WINDOW=1024 SYN # [UFW ALLOW] IN=eth0 OUT= SRC=10.0.0.20 DST=10.0.0.5 # PROTO=TCP SPT=55012 DPT=22 WINDOW=65535 SYN # Count blocked attempts by source IP in the last 100 lines $ grep "UFW BLOCK" /var/log/ufw.log | awk '{for(i=1;i<=NF;i++) if($i~/^SRC=/) print substr($i,5)}' | sort | uniq -c | sort -rn | head -20
Slide 22 of 40
Hardening Script: Minimum Viable Firewall
A complete, safe, copy-ready script for hardening a fresh Ubuntu 22.04 server.
#!/bin/bash # minimum-firewall.sh -- safe baseline for Ubuntu 22.04 servers # Adjust SSH_PORT and ADMIN_NET before running SSH_PORT=22 ADMIN_NET="10.0.0.0/24" # restrict SSH to this CIDR # Flush existing rules iptables -F iptables -X # Accept loopback and established traffic first iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP # SSH from admin network only iptables -A INPUT -p tcp -s $ADMIN_NET --dport $SSH_PORT -j ACCEPT # HTTP and HTTPS iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT # Log and drop everything else iptables -A INPUT -m limit --limit 10/min -j LOG --log-prefix "IPT-DROP: " iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # Save rules netfilter-persistent save
Slide 23 of 40
mangle Table: Packet Header Manipulation
Modify TTL, TOS, and mark packets for advanced routing and QoS scenarios.
TTL Manipulation
TTL (Time To Live) decrements by 1 at each router hop. When it reaches 0 the packet is dropped. You can set a custom TTL on outgoing packets -- useful for camouflaging a router as a single hop or normalizing TTL values across flows.
MARK Target
Apply a numeric mark to a packet without changing its headers. The mark travels with the packet through the kernel and can be used by policy routing (ip rule), traffic shaping (tc), or matched in subsequent iptables rules with -m mark --mark.
# Set outgoing TTL to 64 on all output packets $ iptables -t mangle -A OUTPUT -j TTL --ttl-set 64 # Mark packets from a specific source for policy routing $ iptables -t mangle -A PREROUTING -s 10.0.1.0/24 -j MARK --set-mark 100 # Use the mark in ip rule to route marked packets through a different table $ ip rule add fwmark 100 table 100 $ ip route add default via 192.168.2.1 table 100 # List mangle table rules $ iptables -t mangle -L -v -n
Slide 24 of 40
Custom Chains: Modular Rulesets
Create named chains to organize rules logically and enable jump-based branching.
You have 30 INPUT rules and they are getting hard to read. Custom chains let you group related rules -- all SSH rules in one chain, all web rules in another -- and jump to them from the main chain. The result is an organized, maintainable ruleset.
# Create custom chains $ iptables -N SSH-RULES $ iptables -N WEB-RULES $ iptables -N LOG-AND-DROP # Populate the SSH-RULES chain $ iptables -A SSH-RULES -s 10.0.0.0/24 -j ACCEPT $ iptables -A SSH-RULES -j LOG --log-prefix "SSH-DENIED: " $ iptables -A SSH-RULES -j DROP # Populate the LOG-AND-DROP chain $ iptables -A LOG-AND-DROP -m limit --limit 5/min -j LOG --log-prefix "DROP: " $ iptables -A LOG-AND-DROP -j DROP # Jump to custom chains from INPUT $ iptables -A INPUT -p tcp --dport 22 -j SSH-RULES $ iptables -A INPUT -p tcp -m multiport --dports 80,443 -j WEB-RULES $ iptables -A INPUT -j LOG-AND-DROP
Slide 25 of 40
IPv6 Firewall Considerations
iptables only covers IPv4. IPv6 traffic needs ip6tables or nftables.
The IPv6 Gap
A common mistake: you configure iptables on a dual-stack server and feel protected. But iptables ignores IPv6 entirely. If your server has an IPv6 address (most Ubuntu 22.04 installs do), all your IPv4 rules leave IPv6 traffic completely unfiltered.
ip6tables and UFW
Use ip6tables with identical rules to your iptables config for IPv6 traffic. UFW automatically manages both IPv4 and IPv6 when you add rules -- one ufw allow ssh creates rules in both stacks. This is a strong argument for using UFW on dual-stack hosts.
# Check if IPv6 is configured on your interfaces $ ip -6 addr show # Apply the same rules to IPv6 using ip6tables $ ip6tables -A INPUT -i lo -j ACCEPT $ ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT $ ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT # ICMPv6 is required for IPv6 to function -- do not drop it all $ ip6tables -A INPUT -p ipv6-icmp -j ACCEPT $ ip6tables -P INPUT DROP # UFW handles both stacks transparently $ ufw allow ssh # creates both iptables and ip6tables rules # Verify UFW dual-stack status in /etc/default/ufw $ grep IPV6 /etc/default/ufw IPV6=yes
Slide 26 of 40
Troubleshooting: Diagnosing Firewall Problems
Connection failing? Follow this diagnostic workflow before assuming the application is broken.
# Step 1: Is the port even open and listening? $ ss -tlnp # list TCP listening ports $ ss -ulnp # list UDP listening ports # Step 2: Check what iptables rules are active $ iptables -L INPUT -n -v --line-numbers # Step 3: Test connectivity from another host $ nc -zv 10.0.0.5 22 # test TCP port 22 $ nc -zuv 10.0.0.5 53 # test UDP port 53 # Step 4: Watch the drop log $ dmesg -w | grep IPT-DROP # Step 5: Temporarily flush all rules to isolate firewall vs application $ iptables -F # DANGEROUS on production -- only on a test system # If it works now, the firewall was blocking it # Step 6: Check UFW status if using UFW $ ufw status verbose $ ufw status numbered
Slide 27 of 40
firewalld: The RHEL/CentOS Alternative
You will encounter firewalld in the field. Know the basics even on Ubuntu.
firewall-cmd Runtime (live now) lost on reload --permanent saved to disk --reload applies permanent to runtime
What Is firewalld?
firewalld is the default firewall manager on RHEL, CentOS, Fedora, and Rocky Linux. It uses a zone-based model and provides both a CLI (firewall-cmd) and a D-Bus API. Concepts: zones (trusted, public, internal), services, ports, rich rules, runtime vs permanent configuration.
Runtime vs Permanent
firewalld changes are runtime by default -- lost on reload. Add --permanent to persist a rule. Then run firewall-cmd --reload to apply permanent rules to the running configuration. This two-step model prevents accidental lockout during testing.
# firewalld on RHEL/CentOS -- for your cross-platform awareness # Check status $ systemctl status firewalld $ firewall-cmd --state # List active zones and their interfaces $ firewall-cmd --get-active-zones # Allow SSH permanently in the public zone $ firewall-cmd --zone=public --add-service=ssh --permanent $ firewall-cmd --reload # Allow a specific port $ firewall-cmd --zone=public --add-port=8080/tcp --permanent # Note: Ubuntu uses UFW. firewalld can be installed but is not the default. $ apt install firewalld # possible but may conflict with UFW
Slide 28 of 40
nftables Sets and Maps
Efficient multi-value matching without writing one rule per IP or port.
You need to block 50 known malicious IP addresses. Writing 50 DROP rules is inefficient and slow -- each packet must match against all 50 rules sequentially. An nftables set stores all 50 IPs in a hash table and checks membership in O(1) time.
# nftables set -- block a list of IPs efficiently table ip filter { # Define a named set of blocked addresses set blocklist { type ipv4_addr elements = { 203.0.113.1, 203.0.113.2, 198.51.100.5 } } chain input { type filter hook input priority 0; policy drop; iif lo accept ct state established,related accept # Drop packets from any IP in the blocklist set ip saddr @blocklist drop tcp dport 22 accept } } # Add an IP to the set without reloading the full ruleset $ nft add element ip filter blocklist { 203.0.113.99 } # Remove an IP from the set $ nft delete element ip filter blocklist { 203.0.113.99 }
Slide 29 of 40
Testing Your Firewall Rules
Never assume your rules work. Verify them from the outside.
# Test from another machine with nmap (comprehensive port scan) $ nmap -sS -p 1-1000 10.0.0.5 # SYN scan ports 1-1000 $ nmap -sU -p 53,123,161 10.0.0.5 # UDP scan specific ports $ nmap -sV -p 22,80,443 10.0.0.5 # version detection on expected open ports # Test with netcat (quick, no nmap required) $ nc -zv 10.0.0.5 22 # open: Connection to 10.0.0.5 22 port [tcp/ssh] succeeded! $ nc -zv 10.0.0.5 3306 # closed: nc: connect to 10.0.0.5 port 3306 (tcp) failed # Test with curl for web ports $ curl -I http://10.0.0.5 # should get HTTP response headers $ curl -I https://10.0.0.5 # HTTPS # Verify UFW status shows expected rules $ ufw status verbose # Check iptables packet counters to confirm traffic is hitting rules $ iptables -L INPUT -v -n # pkts and bytes columns show rule hits
Slide 30 of 40
ipset: Efficient IP Blocklists with iptables
The iptables equivalent of nftables sets -- O(1) membership lookups for large address lists.
# Install ipset $ apt install ipset # Create a hash:ip set named "blocklist" $ ipset create blocklist hash:ip # Add IPs to the set $ ipset add blocklist 203.0.113.1 $ ipset add blocklist 198.51.100.5 # Load a list of IPs from file $ while read ip; do ipset add blocklist $ip; done < /etc/blocklist.txt # Create an iptables rule referencing the set $ iptables -I INPUT 1 -m set --match-set blocklist src -j DROP # Save and restore ipset on reboot $ ipset save > /etc/ipset.rules $ ipset restore < /etc/ipset.rules # List contents of the blocklist set $ ipset list blocklist
Slide 31 of 40
Full UFW Deployment Workflow
End-to-end: configure a web server firewall safely without locking yourself out.
#!/bin/bash # ufw-webserver-setup.sh -- safe UFW config for an Ubuntu 22.04 web server # Verify UFW is installed apt install -y ufw # Set default policies (before enabling) ufw default deny incoming ufw default allow outgoing # Allow SSH (CRITICAL -- before enabling or you lock yourself out) ufw allow ssh # Rate-limit SSH to slow brute-force ufw limit ssh # Allow web traffic ufw allow http ufw allow https # Enable the firewall ufw --force enable # Enable logging at medium level ufw logging medium # Verify final state ufw status verbose ufw status numbered
Slide 32 of 40
Anti-Spoofing: Blocking Forged Source Addresses
Drop packets that claim to come from addresses they cannot legitimately originate from.
What Is Spoofing?
An attacker crafts a packet with a forged source IP. Common techniques: claiming to be from loopback (127.0.0.0/8) while arriving on an external interface, or claiming to be from your own private network when arriving from the public internet. Used for DDoS reflection, bypass rules based on source IP.
Reverse Path Filtering
The kernel's rp_filter sysctl automatically drops packets whose source address is not reachable via the interface they arrived on. This is the primary anti-spoofing mechanism. Set to strict mode (value 1) on all interfaces except multi-homed setups.
# Enable strict reverse path filtering (prevents IP spoofing) $ sysctl -w net.ipv4.conf.all.rp_filter=1 $ sysctl -w net.ipv4.conf.default.rp_filter=1 # Persist rp_filter across reboots $ echo "net.ipv4.conf.all.rp_filter = 1" >> /etc/sysctl.conf $ sysctl -p # iptables anti-spoofing: drop packets on eth0 that claim private/loopback source $ iptables -A INPUT -i eth0 -s 127.0.0.0/8 -j DROP $ iptables -A INPUT -i eth0 -s 10.0.0.0/8 -j DROP $ iptables -A INPUT -i eth0 -s 172.16.0.0/12 -j DROP $ iptables -A INPUT -i eth0 -s 192.168.0.0/16 -j DROP
Slide 33 of 40
SYN Flood Protection
Mitigate TCP SYN flood attacks through kernel tuning and iptables rate limits.
# Kernel-level SYN flood mitigations # Enable SYN cookies (recommended always-on) $ sysctl -w net.ipv4.tcp_syncookies=1 # Increase the backlog queue for half-open connections $ sysctl -w net.ipv4.tcp_max_syn_backlog=2048 # Reduce retries for unacknowledged SYN-ACK (default 5, set to 2) $ sysctl -w net.ipv4.tcp_synack_retries=2 # iptables rate-limit NEW TCP connections globally (anti-SYN-flood) $ iptables -A INPUT -p tcp --syn -m limit \ --limit 25/s --limit-burst 50 -j ACCEPT $ iptables -A INPUT -p tcp --syn -j DROP # Persist kernel settings $ cat >> /etc/sysctl.conf << EOF net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 EOF $ sysctl -p
Slide 34 of 40
iptables vs nftables: Side-by-Side Comparison
The same task, two syntaxes. nftables is the future -- know both for the field.
iptables Strengths
Ubiquitous -- every Linux admin knows it. Massive documentation base. Existing rules and scripts are everywhere. Many third-party tools (fail2ban, Docker) generate iptables rules directly. Stable and battle-tested for 20+ years.
nftables Strengths
Unified -- one tool for IPv4, IPv6, ARP, bridges. Better performance at scale. Atomic ruleset updates. Native set/map support. Cleaner, more readable syntax. The kernel maintainer's recommended direction. Will eventually replace iptables.
# The same rule in both tools: # Allow TCP port 22 from 10.0.0.0/24, drop everything else on INPUT -- iptables -- iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT iptables -P INPUT DROP -- nftables -- table ip filter { chain input { type filter hook input priority 0; policy drop; ip saddr 10.0.0.0/24 tcp dport 22 accept } } # Convert existing iptables rules to nftables syntax $ iptables-save > rules.iptables $ iptables-restore-translate -f rules.iptables > /etc/nftables.conf
Slide 35 of 40
Outbound Filtering: Locking Down OUTPUT
Default ACCEPT on OUTPUT is fine for most servers. High-security environments restrict egress too.
A compromised web server calling out to a C2 server on port 4444 is a sign of active compromise. If your OUTPUT policy drops everything except known-good destinations, that callout fails and your monitoring catches the blocked attempt.
# Strict OUTPUT policy -- only allow what is needed # Allow loopback output $ iptables -A OUTPUT -o lo -j ACCEPT # Allow DNS queries (required for apt, curl, basically everything) $ iptables -A OUTPUT -p udp --dport 53 -j ACCEPT $ iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT # Allow outbound HTTP/HTTPS (for apt, curl, updates) $ iptables -A OUTPUT -p tcp -m multiport --dports 80,443 -j ACCEPT # Allow NTP (system clock sync) $ iptables -A OUTPUT -p udp --dport 123 -j ACCEPT # Allow established connections to reply (stateful) $ iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Log and drop all other outbound traffic $ iptables -A OUTPUT -j LOG --log-prefix "IPT-EGRESS-DROP: " $ iptables -P OUTPUT DROP
Slide 36 of 40
Docker and iptables Conflicts
Docker aggressively manipulates iptables. Know this before you wonder why your rules are being bypassed.
The Docker Problem
Docker daemon inserts its own iptables chains (DOCKER, DOCKER-ISOLATION-STAGE-1, DOCKER-USER) and adds ACCEPT rules for every published port. Your UFW deny rules may be bypassed if Docker publishes a port directly to 0.0.0.0. This is a well-known security issue.
The Fix
Bind Docker containers to localhost (127.0.0.1:8080:80 instead of 8080:80) and expose them via a reverse proxy. Or use the DOCKER-USER chain for rules that Docker will not override. Or set iptables: false in /etc/docker/daemon.json and manage manually.
# See what Docker added to iptables $ iptables -L -n -v | grep -i docker # DOCKER-USER chain -- Docker will not touch rules here $ iptables -I DOCKER-USER -s 0.0.0.0/0 -j DROP $ iptables -I DOCKER-USER -s 10.0.0.0/24 -j RETURN # allow your admin CIDR # Bind a container to localhost only (correct approach) # docker run -p 127.0.0.1:8080:80 nginx # Disable Docker iptables management (advanced -- requires full manual setup) # Add to /etc/docker/daemon.json: # { "iptables": false }
Slide 37 of 40
Verifying Saved Rules Survive Reboot
Confirm your persistent firewall configuration loads correctly after a system restart.
# Step 1: View currently active rules $ iptables -L -v -n # Step 2: Save current rules to the persistence file $ iptables-save > /etc/iptables/rules.v4 # Step 3: Verify the saved file is correct $ cat /etc/iptables/rules.v4 # Step 4: Simulate a reboot restore without rebooting $ iptables -F # flush all rules $ iptables-restore < /etc/iptables/rules.v4 $ iptables -L -v -n # verify rules are back # Step 5: Confirm the service is enabled for boot $ systemctl is-enabled netfilter-persistent # For UFW -- verify it survives reboot $ systemctl is-enabled ufw $ ufw status # should show "Status: active" after reboot
Slide 38 of 40
Common Firewall Mistakes
Learn from the errors that cause lockouts, bypasses, and breaches.
1 Setting -P INPUT DROP before adding your SSH ACCEPT rule. You will lock yourself out of the server instantly.
2 Not saving rules after configuration. After reboot, all your work is gone. Always run netfilter-persistent save or iptables-save.
3 Forgetting IPv6. iptables rules do not apply to IPv6. Use ip6tables or UFW to cover both stacks.
4 Relying on source IP filtering alone for access control. IP spoofing, VPNs, and NAT make source IP unreliable. Layer authentication on top.
5 Not testing from the outside. Rules that look correct can have ordering bugs. Always verify from another host using nmap or nc.
6 Forgetting established traffic. If you add DROP to OUTPUT or INPUT without first allowing ESTABLISHED,RELATED, active sessions break immediately.
7 Using REJECT instead of DROP on public interfaces. REJECT tells attackers exactly what is filtered. Use DROP to reveal nothing.
Slide 39 of 40
Key Vocabulary
Terms you will encounter in documentation, job interviews, and security audits.
Firewall Architecture Terms
netfilter -- kernel framework that provides the packet filtering hooks.
chain -- an ordered list of rules evaluated against a packet.
table -- a grouping of chains by purpose (filter, nat, mangle).
policy -- the default verdict for a built-in chain when no rule matches.
stateful inspection -- tracking connection state to make filtering decisions.
Rule and Target Terms
ACCEPT -- allow the packet through, stop rule processing.
DROP -- discard silently, no reply to sender.
REJECT -- discard and notify sender with ICMP error.
LOG -- record packet info, continue rule processing.
DNAT -- rewrite destination address/port (port forwarding).
MASQUERADE -- rewrite source to outgoing interface IP.
Defensive Strategy Terms
default deny -- block everything; only explicitly allow what is needed.
defense in depth -- multiple independent security layers.
zone-based policy -- different rules per network segment or interface.
egress filtering -- restricting outbound traffic from a host.
rp_filter -- reverse path filtering; kernel anti-spoofing mechanism.
Slide 40 of 40  |  Week 2 Topic 1
Perimeter Shield: Key Takeaways
Your cell's firewall is now configured. iptables, nftables, and UFW all write to the same netfilter kernel hooks. The tool is a preference; the logic is universal: default deny, allow only what is needed, log what you drop, verify from outside, save your rules.
1 All Linux firewall tools (iptables, nftables, UFW) write rules to the kernel's netfilter hooks. They are frontends to the same engine.
2 The three filter chains: INPUT (to this machine), OUTPUT (from this machine), FORWARD (through this machine).
3 Always add ACCEPT rules before setting -P INPUT DROP. Wrong order = instant lockout from SSH.
4 Use -m conntrack --ctstate ESTABLISHED,RELATED to allow reply traffic without opening ports permanently.
5 Use DROP on public interfaces. REJECT tells attackers the port is filtered. DROP reveals nothing.
6 iptables rules are volatile. Persist them with netfilter-persistent save or iptables-save.
7 UFW handles IPv4 and IPv6 simultaneously. Raw iptables requires separate ip6tables commands.
8 nftables is the modern replacement. Ubuntu 22.04 already runs iptables-nft under the hood.
9 Always verify your rules from outside with nmap or nc. Correct-looking rules can have ordering bugs that only external testing catches.