Overview
The Linux firewall has evolved from ipfwadm → ipchains → iptables → nftables. All are based on the Netfilter framework, but the higher-level syntax and management approach have continuously improved. This article starts from the Netfilter architecture and dives into iptables’ five chains and four tables, nftables’ advantages and usage, NAT/port forwarding, connection tracking, and production performance optimization.
Netfilter Framework
Architecture Overview
Netfilter is a packet processing framework in the Linux kernel that implements packet filtering, address translation, connection tracking, and other functions by mounting hooks at key positions in the kernel network stack.
Netfilter Hook Points
[Packet enters] → PREROUTING → [Routing decision] →─┬─→ FORWARD → POSTROUTING → [Packet leaves]
│
└─→ INPUT → [Local process] → OUTPUT → POSTROUTING → [Packet leaves]
Five Hook Points
| Hook Point | Trigger Timing | Description |
|---|---|---|
NF_INET_PRE_ROUTING | Packet enters network stack, before routing | Pre-routing |
NF_INET_LOCAL_IN | Packet destination is this host | Input |
NF_INET_FORWARD | Packet needs forwarding to another interface | Forward |
NF_INET_LOCAL_OUT | Packet generated by this host | Output |
NF_INET_POST_ROUTING | Packet about to leave the network stack | Post-routing |
Packet Flow
Inbound (to this host):
NIC → PREROUTING → INPUT → local process
Outbound (from this host):
Local process → OUTPUT → POSTROUTING → NIC
Forward (through this host):
NIC → PREROUTING → FORWARD → POSTROUTING → NIC
iptables Five Chains and Four Tables
Four Tables
iptables organizes rule chains of different functions through “tables”:
| Table | Function | Chains |
|---|---|---|
raw | Process before connection tracking (before CONNTRACK) | PREROUTING, OUTPUT |
mangle | Modify packet TTL/TOS/Mark etc. | All five chains |
nat | Address translation (DNAT/SNAT) | PREROUTING, OUTPUT, POSTROUTING, INPUT |
filter | Packet filtering (ACCEPT/DROP/REJECT) | INPUT, FORWARD, OUTPUT |
Table priority: raw → mangle → nat → filter. When a packet passes through a hook point, rules from each table are matched in this order.
Five Chains
| Chain | Belonging Tables | Description |
|---|---|---|
INPUT | filter, mangle, nat | Inbound packets |
OUTPUT | raw, mangle, nat, filter | Outbound packets |
FORWARD | filter, mangle | Forwarded packets |
PREROUTING | raw, mangle, nat | Pre-routing |
POSTROUTING | mangle, nat | Post-routing |
Rule Matching Flow
Packet enters hook point
│
▼
[raw table] ──→ [mangle table] ──→ [nat table] ──→ [filter table]
│ │ │ │
│ │ │ ├─→ ACCEPT
│ │ │ ├─→ DROP (no response)
│ │ │ ├─→ REJECT (returns ICMP)
│ │ │ └─→ RETURN (return to previous chain)
│ │ └─→ DNAT/SNAT
│ └─→ Modify TTL/TOS/Mark
└─→ NOTRACK (do not track connection)
Basic iptables Commands
# View rules
$ iptables -L -n -v --line-numbers
$ iptables -t nat -L -n -v
# Append rule
$ iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Insert rule (before rule 1)
$ iptables -I INPUT 1 -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
# Delete rule
$ iptables -D INPUT 3 # Delete rule 3
$ iptables -D INPUT -p tcp --dport 22 -j ACCEPT # Delete by content
# Set default policy
$ iptables -P INPUT DROP
$ iptables -P FORWARD DROP
$ iptables -P OUTPUT ACCEPT
Common Match Conditions
# Protocol matching
$ iptables -A INPUT -p tcp ...
$ iptables -A INPUT -p udp ...
$ iptables -A INPUT -p icmp ...
# Port matching
$ iptables -A INPUT -p tcp --dport 80 # Destination port
$ iptables -A INPUT -p tcp --sport 1024:65535 # Source port range
# IP matching
$ iptables -A INPUT -s 10.0.0.0/8 # Source address
$ iptables -A INPUT -d 192.168.1.1 # Destination address
# Interface matching
$ iptables -A INPUT -i eth0 # Inbound interface
$ iptables -A OUTPUT -o eth1 # Outbound interface
# Connection state matching
$ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Rate limiting
$ iptables -A INPUT -p tcp --dport 22 -m limit --limit 5/min --limit-burst 10 -j ACCEPT
# IP sets
$ iptables -A INPUT -m set --match-set blacklist src -j DROP
# String matching
$ iptables -A INPUT -p tcp --dport 80 -m string --string "GET /admin" --algo bm -j DROP
# Time matching
$ iptables -A INPUT -p tcp --dport 22 -m time --timestart 09:00 --timestop 18:00 --weekdays Mon,Tue,Wed,Thu,Fri -j ACCEPT
Production Baseline Rule Set
#!/bin/bash
# /etc/iptables/rules.sh - Production firewall rules
# Flush all rules
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -X
# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow ICMP (rate-limited)
iptables -A INPUT -p icmp -m limit --limit 1/s -j ACCEPT
# SSH: allow internal network, rate-limit external
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m limit --limit 3/min --limit-burst 5 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
# Web services
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# Monitoring (Prometheus node_exporter)
iptables -A INPUT -p tcp --dport 9100 -s 10.0.0.100 -j ACCEPT
# Log rejected connections
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables-dropped: " --log-level 4
iptables -A INPUT -j DROP
# Save rules
iptables-save > /etc/iptables/rules.v4
nftables
nftables Advantages
| Feature | iptables | nftables |
|---|---|---|
| Syntax | Fragmented (iptables/ip6tables/arptables/ebtables) | Unified |
| Performance | Each rule independent | Batch rule set loading |
| Rule updates | Append one by one, race conditions | Atomic replacement |
| Data types | Untyped | Strongly typed |
| Sets | Limited (ipset) | Native support |
| Maps | Not supported | Supported |
| Tables/chains | Fixed | Customizable |
| Connection tracking | conntrack module | Native ct object |
nftables Basic Concepts
# Install
$ apt install nftables # Debian/Ubuntu
$ dnf install nftables # RHEL/CentOS
# View rule set
$ nft list ruleset
Tables and Chains
# Create table (specify protocol family)
$ nft add table inet filter # IPv4+IPv6
$ nft add table ip nat # IPv4 only
$ nft add table ip6 filter # IPv6 only
$ nft add table bridge filter # Bridge
# Protocol families
# inet: IPv4 + IPv6 (recommended)
# ip: IPv4
# ip6: IPv6
# arp: ARP
# bridge: Bridge
# netdev: Ingress (earliest, before routing)
# Create chain
$ nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
$ nft add chain inet filter forward '{ type filter hook forward priority 0; policy drop; }'
$ nft add chain inet filter output '{ type filter hook output priority 0; policy accept; }'
# Create custom chain
$ nft add chain inet filter log_drop
$ nft add rule inet filter log_drop log prefix "nft-dropped: " counter drop
Rule Writing
# Basic rules
$ nft add rule inet filter input iifname "lo" accept
$ nft add rule inet filter input ct state established,related accept
$ nft add rule inet filter input icmp type echo-request limit rate 1/second accept
# Port rules
$ nft add rule inet filter input tcp dport 22 accept
$ nft add rule inet filter input tcp dport { 80, 443 } accept
$ nft add rule inet filter input tcp dport 1000-2000 accept
# Source address
$ nft add rule inet filter input ip saddr 10.0.0.0/8 tcp dport 22 accept
$ nft add rule inet filter input ip6 saddr fd00::/8 tcp dport 22 accept
# Counters
$ nft add rule inet filter input tcp dport 80 counter accept
$ nft list ruleset
# tcp dport 80 counter packets 12345 bytes 6789012 accept
Sets and Maps
# Create set
$ nft add set inet filter blacklist '{ type ipv4_addr; flags interval; }'
$ nft add element inet filter blacklist '{ 192.168.1.100, 10.0.0.0/8 }'
# Use set
$ nft add rule inet filter input ip saddr @blacklist drop
# Create map
$ nft add map inet filter port_map '{ type inet_service : verdict; }'
$ nft add element inet filter port_map '{ 22 : accept, 80 : accept, 443 : accept }'
# Use map
$ nft add rule inet filter input tcp dport vmap @port_map
nftables Configuration File
# /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set blacklist {
type ipv4_addr
flags interval
elements = { 10.0.0.0/8, 192.168.1.100 }
}
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iifname "lo" accept
# Established connections
ct state established,related accept
# ICMP
icmp type echo-request limit rate 1/second accept
icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert } accept
# SSH
tcp dport 22 ip saddr 10.0.0.0/8 accept
tcp dport 22 limit rate 3/minute burst 5 packets accept
# Web
tcp dport { 80, 443 } accept
# Monitoring
tcp dport 9100 ip saddr 10.0.0.100 accept
# Blacklist
ip saddr @blacklist drop
# Log and drop
limit rate 5/minute log prefix "nft-dropped: " level warn
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
# Load configuration
$ nft -f /etc/nftables.conf
# Enable on boot
$ systemctl enable nftables
iptables to nftables Migration
# Use iptables-nft backend (compatibility layer)
$ update-alternatives --set iptables /usr/sbin/iptables-nft
$ update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
# Convert iptables rules to nftables
$ iptables-save > rules.v4
$ iptables-restore-translate -f rules.v4 > rules.nft
$ nft -f rules.nft
# RHEL 8+ uses nftables backend by default
# firewalld backend configuration
# /etc/firewalld/firewalld.conf
FirewallBackend=nftables
NAT and Port Forwarding
SNAT (Source Address Translation)
# iptables: Change source address of internal 10.0.0.0/8 to public IP
$ iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j SNAT --to-source 203.0.113.1
# MASQUERADE (dynamic IP, e.g., DHCP/PPPoE)
$ iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j MASQUERADE
# nftables
$ nft add table ip nat
$ nft 'add chain ip nat postrouting { type nat hook postrouting priority 100; }'
$ nft add rule ip nat postrouting ip saddr 10.0.0.0/8 oifname "eth0" masquerade
DNAT (Destination Address Translation / Port Forwarding)
# iptables: Forward traffic to 203.0.113.1:80 to 10.0.0.10:8080
$ iptables -t nat -A PREROUTING -d 203.0.113.1 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.10:8080
$ iptables -t nat -A POSTROUTING -d 10.0.0.10 -p tcp --dport 8080 -j SNAT --to-source 10.0.0.1
$ iptables -A FORWARD -p tcp -d 10.0.0.10 --dport 8080 -j ACCEPT
# Local port forwarding
$ iptables -t nat -A OUTPUT -d 127.0.0.1 -p tcp --dport 80 -j REDIRECT --to-ports 8080
# nftables
$ nft 'add chain ip nat prerouting { type nat hook prerouting priority -100; }'
$ nft add rule ip nat prerouting tcp dport 80 dnat to 10.0.0.10:8080
Port Range Forwarding
# Forward ports 20000-30000 to internal server
$ iptables -t nat -A PREROUTING -p tcp --dport 20000:30000 -j DNAT --to-destination 10.0.0.10
$ iptables -A FORWARD -p tcp -d 10.0.0.10 --dport 20000:30000 -j ACCEPT
Transparent Proxy
# Redirect all HTTP traffic to Squid proxy (port 3128)
$ iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80 -j REDIRECT --to-ports 3128
$ iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner squid -j RETURN
$ iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 3128
Connection Tracking
How It Works
Connection tracking (conntrack) records the state of each network connection, used for state matching (-m conntrack --ctstate) and NAT.
Connection States
| State | Description |
|---|---|
NEW | First packet of a new connection |
ESTABLISHED | Connection established (bidirectional communication) |
RELATED | Related to an existing connection (e.g., FTP data connection, ICMP error) |
INVALID | Unrecognized packet |
SNAT/DNAT | Connection that has undergone SNAT/DNAT |
conntrack Table Management
# View conntrack table size
$ cat /proc/sys/net/netfilter/nf_conntrack_max
262144
# View current connection count
$ cat /proc/sys/net/netfilter/nf_conntrack_count
12345
# View connection tracking statistics
$ cat /proc/net/nf_conntrack | head -20
# src=10.0.0.1 dst=93.184.216.34 sport=54321 dport=443 protocol=tcp state=ESTABLISHED ...
# Count connections by state
$ cat /proc/net/nf_conntrack | awk '{print $4}' | sort | uniq -c | sort -rn
conntrack Timeout Parameters
# View timeout parameters
$ ls /proc/sys/net/netfilter/nf_conntrack_*_timeout_*
/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established # 432000 (5 days)
/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_sent # 120
/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv # 60
/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_fin_wait # 120
/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_time_wait # 120
/proc/sys/net/netfilter/nf_conntrack_udp_timeout # 30
/proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream # 180
# Adjust TCP established timeout (default 5 days is too long)
$ sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400 # 1 day
$ sysctl -w net.netfilter.nf_conntrack_udp_timeout=15
Consequences of conntrack Table Full
When the conntrack table is full, new connections are dropped, and the log shows:
nf_conntrack: table full, dropping packet
Solution:
# Temporarily increase table size
$ sysctl -w net.netfilter.nf_conntrack_max=524288
# Permanent configuration
# /etc/sysctl.d/conntrack.conf
net.netfilter.nf_conntrack_max = 524288
net.netfilter.nf_conntrack_buckets = 131072 # Must be power of 2, typically = max/4
conntrack Tools
# conntrack command-line tool
$ apt install conntrack
# View connections
$ conntrack -L
# View statistics
$ conntrack -S
# Delete specific connection
$ conntrack -D -s 10.0.0.1 -p tcp --dport 443
# Flush all connections
$ conntrack -F
# Real-time monitoring
$ conntrack -E
Performance Optimization
Rule Order Optimization
iptables rules match in order — place high-hit rules first:
# Bad order: SSH after Web
$ iptables -A INPUT -p tcp --dport 80 -j ACCEPT # High frequency but placed later
$ iptables -A INPUT -p tcp --dport 443 -j ACCEPT
$ iptables -A INPUT -p tcp --dport 22 -j ACCEPT # Low frequency but placed first
# Good order: by traffic volume, high to low
$ iptables -A INPUT -p tcp --dport 443 -j ACCEPT # Highest frequency
$ iptables -A INPUT -p tcp --dport 80 -j ACCEPT
$ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Highest frequency
$ iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Using ipset/nftables Sets
# iptables + ipset: replace multiple rules
$ ipset create whitelist hash:net
$ ipset add whitelist 10.0.0.0/8
$ ipset add whitelist 192.168.0.0/16
# One rule replaces many
$ iptables -A INPUT -m set --match-set whitelist src -j ACCEPT
# Instead of
# iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT
# iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT
Disabling Unneeded Connection Tracking
# Skip connection tracking for traffic that doesn't need NAT
$ iptables -t raw -A PREROUTING -i lo -j NOTRACK
$ iptables -t raw -A OUTPUT -o lo -j NOTRACK
# Skip connection tracking for internal traffic
$ iptables -t raw -A PREROUTING -s 10.0.0.0/8 -d 10.0.0.0/8 -j NOTRACK
# nftables
$ nft add table inet raw
$ nft 'add chain inet raw prerouting { type filter hook prerouting priority -300; }'
$ nft add rule inet raw prerouting iifname "lo" notrack
Using SYNPROXY to Defend Against SYN Flood
# Use SYNPROXY for inbound TCP connections
$ iptables -t raw -A PREROUTING -p tcp -m tcp --syn -j CT --notrack
$ iptables -A INPUT -p tcp -m tcp --syn -m conntrack --ctstate INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
$ iptables -A INPUT -p tcp -m conntrack --ctstate INVALID -j DROP
Analyzing Rule Hit Rates
# View hit count per rule
$ iptables -L -n -v --line-numbers
# Example output
Chain INPUT (policy DROP 56789 packets, 1234KB)
num pkts bytes target prot opt in out source destination
1 9876 456K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
2 1234 56K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
3 87654 12M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
# Adjust rule order based on hit rate: high-hit rules go first
Real-World Cases
Case 1: Kubernetes Node Firewall
#!/bin/bash
# K8s node firewall rules
iptables -F
iptables -t nat -F
iptables -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT # K8s needs forwarding
iptables -P OUTPUT ACCEPT
# Loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i cni0 -j ACCEPT # CNI bridge
iptables -A INPUT -i docker0 -j ACCEPT # Docker bridge
# Established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# K8s components
iptables -A INPUT -p tcp --dport 6443 -s 10.0.0.0/8 -j ACCEPT # API Server
iptables -A INPUT -p tcp --dport 10250 -s 10.0.0.0/8 -j ACCEPT # kubelet
iptables -A INPUT -p tcp --dport 10257 -s 10.0.0.0/8 -j ACCEPT # controller-manager
iptables -A INPUT -p tcp --dport 10259 -s 10.0.0.0/8 -j ACCEPT # scheduler
# NodePort range
iptables -A INPUT -p tcp --dport 30000:32767 -j ACCEPT
# ICMP
iptables -A INPUT -p icmp -j ACCEPT
# SSH
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
Case 2: Container Port Mapping Internals
Docker port mapping uses iptables NAT rules under the hood:
# Rules generated by: docker run -p 8080:80 nginx
# DNAT: forward host port 8080 to container port 80
$ iptables -t nat -L -n -v | grep 8080
DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
# SNAT: change container outbound source address to host IP
$ iptables -t nat -L POSTROUTING -n -v
MASQUERADE tcp -- * * 172.17.0.2 0.0.0.0/0 masq ports: 80
Case 3: High-Concurrency Web Server Firewall Optimization
#!/bin/bash
# High-concurrency web server (100K+ connections)
# 1. Increase conntrack table
echo 1048576 > /proc/sys/net/netfilter/nf_conntrack_max
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize
# 2. Shorten timeouts
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
sysctl -w net.netfilter.nf_conntrack_udp_timeout=15
# 3. Skip connection tracking for web traffic (filter only, no tracking)
iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
iptables -t raw -A PREROUTING -p tcp --dport 443 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 443 -j NOTRACK
# 4. Use stateless filtering (with NOTRACK)
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 443 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
# 5. Limit SYN rate
iptables -A INPUT -p tcp --syn --dport 443 -m limit --limit 1000/s --limit-burst 2000 -j ACCEPT
iptables -A INPUT -p tcp --syn --dport 443 -j DROP
Case 4: Load Balancing with nftables
# Use nftables nat module for round-robin load balancing
$ nft add table ip nat
$ nft 'add chain ip nat prerouting { type nat hook prerouting priority -100; }'
# Distribute port 80 traffic across 3 backend servers (round-robin)
$ nft add rule ip nat prerouting tcp dport 80 dnat to 10.0.0.{10,11,12}
# Or with explicit weights
$ nft add rule ip nat prerouting tcp dport 80 dnat numgen inc mod 3 map { 0: 10.0.0.10, 1: 10.0.0.11, 2: 10.0.0.12 }
Rule Persistence
iptables Persistence
# Debian/Ubuntu
$ apt install iptables-persistent
$ netfilter-persistent save
# Rules saved in /etc/iptables/rules.v4 and rules.v6
# RHEL/CentOS
$ iptables-save > /etc/sysconfig/iptables
$ systemctl enable iptables
# Manual save/restore
$ iptables-save > /path/to/rules.v4
$ iptables-restore < /path/to/rules.v4
nftables Persistence
# Save current rule set
$ nft list ruleset > /etc/nftables.conf
# Enable on boot
$ systemctl enable nftables
Summary
The evolution of Linux firewalls from iptables to nftables reflects a design direction of “unification, simplification, and high performance.” Key takeaways:
- Understand the five Netfilter hook points: PREROUTING → INPUT/OUTPUT/FORWARD → POSTROUTING is the foundation of all rule design.
- iptables four tables have priority: raw → mangle → nat → filter; higher-priority tables match first.
- Rule order is critical: Place high-hit rules first; use counters to analyze hit rates and optimize order.
- nftables is the future direction: Unified syntax, native sets/maps, atomic rule replacement, better performance.
- Connection tracking is a performance bottleneck: In high-concurrency scenarios, increase
nf_conntrack_max, shorten timeouts, and use NOTRACK for traffic that doesn’t need tracking. - ipset/nftables sets can drastically reduce rule count: One rule + one set replaces dozens of individual rules.
- NAT port forwarding is a common function: Understanding the DNAT + SNAT + FORWARD combination is necessary to correctly configure port forwarding.
- SYNPROXY is a powerful SYN Flood defense: It proxies the three-way handshake at the kernel level, filtering out forged-source SYN packets.
Golden rule of firewall configuration: allow first, then block. Before configuring a firewall, ensure SSH connections won’t be accidentally blocked, or you may lock yourself out. Setting up a cron job to automatically flush rules after 5 minutes as a safety net is recommended.