iptables rules are stateful by default, meaning you don’t need to explicitly allow return traffic for established connections.
Let’s craft a robust iptables firewall script that you can adapt for various server setups. This isn’t just a collection of rules; it’s a template designed for clarity, security, and ease of modification.
#!/bin/bash
# --- Configuration ---
# Interface to protect (e.g., eth0, ens3)
INTERFACE="eth0"
# Allowed SSH port (default 22)
SSH_PORT="22"
# Allowed HTTP port (default 80)
HTTP_PORT="80"
# Allowed HTTPS port (default 443)
HTTPS_PORT="443"
# Trusted IP addresses/networks (e.g., your home IP, management subnet)
# Add multiple with spaces: TRUSTED_IPS="1.2.3.4 192.168.1.0/24"
TRUSTED_IPS=""
# Enable logging for dropped packets (0 for no, 1 for yes)
LOG_DROPPED="1"
# --- Script Logic ---
# Flush existing rules and chains
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Set default policies to DROP (most secure)
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT # Usually safe to allow outgoing traffic
# --- Stateful Connection Tracking ---
# Allow all traffic for established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# --- Loopback Interface ---
# Allow all traffic on the loopback interface
iptables -A INPUT -i lo -j ACCEPT
# --- SSH Access ---
# Allow SSH from trusted IPs
if [ -n "$TRUSTED_IPS" ]; then
for ip in $TRUSTED_IPS; do
iptables -A INPUT -p tcp -m tcp --dport "$SSH_PORT" -s "$ip" -j ACCEPT
done
fi
# Allow SSH from any IP (use with extreme caution, consider limiting by IP if possible)
# iptables -A INPUT -p tcp -m tcp --dport "$SSH_PORT" -j ACCEPT
# --- HTTP/HTTPS Access ---
# Allow HTTP traffic
iptables -A INPUT -p tcp -m tcp --dport "$HTTP_PORT" -j ACCEPT
# Allow HTTPS traffic
iptables -A INPUT -p tcp -m tcp --dport "$HTTPS_PORT" -j ACCEPT
# --- ICMP (Ping) ---
# Allow essential ICMP types (echo-request, destination-unreachable, time-exceeded)
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT # echo-request (ping)
iptables -A INPUT -p icmp --icmp-type 3 -j ACCEPT # destination-unreachable
iptables -A INPUT -p icmp --icmp-type 11 -j ACCEPT # time-exceeded
# --- Logging Dropped Packets ---
if [ "$LOG_DROPPED" -eq 1 ]; then
# Log dropped INPUT packets to a separate chain for clarity
iptables -N LOGGING
iptables -A INPUT -j LOGGING
iptables -A LOGGING -m limit --limit 2/min --limit-burst 5 -j LOG --log-prefix "IPTables-Dropped: " --log-level 7
iptables -A LOGGING -j DROP # Ensure dropped packets are actually dropped after logging
fi
# --- Apply rules to the specific interface ---
# This is often redundant if your default policy is DROP and you've explicitly allowed everything else,
# but can be useful for explicit interface binding.
# iptables -A INPUT -i "$INTERFACE" -j ACCEPT # This would override the DROP policy if not careful
# --- Save the rules (distribution dependent) ---
# For Debian/Ubuntu:
# sudo apt-get install iptables-persistent
# sudo netfilter-persistent save
# For RHEL/CentOS/Fedora:
# sudo service iptables save
# OR
# sudo iptables-save > /etc/sysconfig/iptables
echo "Firewall rules applied."
echo "Interface: $INTERFACE"
echo "SSH Port: $SSH_PORT"
echo "HTTP Port: $HTTP_PORT"
echo "HTTPS Port: $HTTPS_PORT"
echo "Trusted IPs: $TRUSTED_IPS"
echo "Logging Dropped: $([ "$LOG_DROPPED" -eq 1 ] && echo "Enabled" || echo "Disabled")"
Let’s break down why this works and how to tailor it.
Core Principles:
- Default Deny (DROP): The most critical security posture is to block everything by default.
iptables -P INPUT DROPensures that any traffic not explicitly allowed by a subsequent rule is discarded. This is far more secure than aREJECTpolicy, which sends back an error, potentially revealing information about your system. - Stateful Inspection:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTis the magic bullet for return traffic. Once a connection is established (e.g., you SSH into the server), the firewall remembers it. This rule allows all packets that are part of an existing, legitimate connection without you needing to create separate rules for outbound requests and their inbound replies. - Least Privilege: Only open the ports and allow access from the IPs that are absolutely necessary. The template provides explicit rules for SSH, HTTP, and HTTPS, and a mechanism for trusted IPs.
Configuration Breakdown:
INTERFACE: This is the network interface your server uses to communicate with the outside world. You can find it usingip addr showorifconfig. For example, on many cloud instances, it’seth0.SSH_PORT,HTTP_PORT,HTTPS_PORT: These variables make it trivial to change default ports if you’ve secured SSH by moving it to a non-standard port or if your web server uses different configurations.TRUSTED_IPS: This is crucial for management. Add your home IP address or your office subnet here. This ensures that only these IPs can connect to sensitive services like SSH. If you need to allow multiple IPs or subnets, separate them with spaces.LOG_DROPPED: Enabling this sends dropped packets tosyslog(usually/var/log/syslogor/var/log/messages). This is invaluable for troubleshooting and security auditing. Thelimitmodule prevents your logs from being flooded.
Key Rule Explanations:
iptables -F,-X,-t nat -F, etc.: These commands flush (delete) all existing rules and user-defined chains. This ensures you’re starting with a clean slate, preventing conflicts with old rules.iptables -P INPUT DROP: Sets the default policy for theINPUTchain toDROP. Packets arriving at the server that don’t match any subsequent rule will be silently discarded.iptables -A INPUT -i lo -j ACCEPT: Thelointerface is the loopback interface. Your system uses this to talk to itself. It’s essential for many local services, so we allow all traffic on it.iptables -A INPUT -p tcp -m tcp --dport "$SSH_PORT" -s "$ip" -j ACCEPT: This is a specific rule.-A INPUT: Appends this rule to theINPUTchain.-p tcp: Matches the TCP protocol.-m tcp --dport "$SSH_PORT": Matches the destination port (e.g., 22 for SSH).-s "$ip": Matches the source IP address. This is whereTRUSTED_IPScome in.-j ACCEPT: If all conditions match, accept the packet.
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT: Allowspingrequests (ICMP type 8). Essential for network diagnostics. Including types 3 (destination-unreachable) and 11 (time-exceeded) helps with routing and network troubleshooting.iptables -N LOGGING: Creates a new chain calledLOGGING. This is a common technique to keep the mainINPUTchain cleaner.iptables -A INPUT -j LOGGING: Any packet that reaches this point in theINPUTchain (meaning it wasn’t accepted by any prior rule) is sent to theLOGGINGchain.iptables -A LOGGING -m limit --limit 2/min --limit-burst 5 -j LOG --log-prefix "IPTables-Dropped: " --log-level 7: In theLOGGINGchain, this rule logs the packet. Thelimitmodule prevents log spam.log-prefixmakes it easy to find these log entries.iptables -A LOGGING -j DROP: After logging, the packet is dropped. This ensures that even if logging fails, the packet is still discarded.
The "One Thing" Nobody Tells You:
The iptables command itself doesn’t run the firewall persistently. It applies the rules in memory for the current session. To make them stick across reboots, you must save them. The method varies by distribution:
- Debian/Ubuntu: Install
iptables-persistent(sudo apt-get install iptables-persistent) and then runsudo netfilter-persistent save. This saves the current rules to/etc/iptables/rules.v4and/etc/iptables/rules.v6. - RHEL/CentOS/Fedora (older): Use
sudo service iptables save. This saves rules to/etc/sysconfig/iptables. - RHEL/CentOS/Fedora (newer, firewalld): If you’re using
firewalld,iptablescommands might be overridden. It’s generally better to stick to one or the other. If you must useiptablesdirectly, you might need to disablefirewalldand useiptables-services(sudo yum install iptables-services,sudo systemctl enable iptables,sudo systemctl start iptables, thensudo iptables-save > /etc/sysconfig/iptables).
Without saving, your firewall will vanish after the next reboot, leaving your server exposed.
Next Steps:
Once your basic firewall is solid, you’ll likely want to explore more advanced iptables features like rate limiting for specific services (e.g., to prevent brute-force attacks on SSH), blocking specific malicious IPs, or setting up port forwarding.