iptables has been the standard Linux packet-filtering firewall for over two decades, and while RHEL 9 ships with nftables and firewalld as the preferred tools, iptables remains available and is widely understood by seasoned administrators. Learning iptables gives you deep insight into how Linux netfilter works and is valuable for managing legacy systems or containers where iptables rules are written directly. This tutorial walks through installing, configuring, and persisting iptables rules on RHEL 9.
Prerequisites
- RHEL 9 system with root or sudo access
- SSH access to the server (keep an active session open while editing firewall rules)
- Basic understanding of TCP/IP networking (ports, protocols, IP addresses)
- firewalld disabled or stopped to avoid conflicts with iptables
Step 1 — Install iptables-services and Disable firewalld
RHEL 9 uses firewalld by default. If you intend to manage rules directly with iptables, disable firewalld first to avoid conflicts, then install the iptables-services package which provides the save/restore service:
# Stop and disable firewalld
systemctl stop firewalld
systemctl disable firewalld
systemctl mask firewalld
# Install iptables-services
dnf install -y iptables-services
# Enable the iptables service
systemctl enable iptables
systemctl start iptables
Check that the service is running and inspect the default rules loaded:
systemctl status iptables
iptables -L -n -v --line-numbers
Step 2 — Understand Tables and Chains
iptables organises rules into tables, each serving a different purpose:
- filter — Default table; handles packet acceptance and dropping (INPUT, OUTPUT, FORWARD chains).
- nat — Network address translation (PREROUTING, POSTROUTING, OUTPUT).
- mangle — Specialised packet alteration.
Within the filter table, the three chains are:
- INPUT — Traffic destined for the local system.
- OUTPUT — Traffic originating from the local system.
- FORWARD — Traffic being routed through the system.
Step 3 — Build a Baseline Ruleset
The following commands build a secure baseline: flush existing rules, set a default DROP policy on INPUT and FORWARD, then allow only what is explicitly needed. Run these commands in order — do not disconnect your SSH session between steps:
# Flush all existing rules
iptables -F
iptables -X
iptables -Z
# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback interface
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established and related connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (port 22) — critical: add this before testing default DROP
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
# Allow HTTP and HTTPS
iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT
# Allow ICMP (ping)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
Step 4 — Add Logging for Dropped Packets
Logging dropped packets helps you diagnose blocked traffic and detect port scans or intrusion attempts. Add a LOG rule before the final implicit DROP so that dropped packets are recorded in /var/log/messages:
# Log dropped INPUT packets (limit to 5 per minute to avoid log flooding)
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables-dropped: " --log-level 4
# View logged drops in real time
tail -f /var/log/messages | grep "iptables-dropped"
Step 5 — Save Rules and Persist Across Reboots
Rules added with iptables commands are in-memory only and will be lost on reboot. The iptables-services package provides a service iptables save command that writes rules to /etc/sysconfig/iptables, which is loaded automatically on boot:
# Save current rules
service iptables save
# Verify the saved rules file
cat /etc/sysconfig/iptables
# Confirm the service is enabled for boot
systemctl is-enabled iptables
To reload the saved rules at any time without rebooting:
systemctl restart iptables
Step 6 — iptables vs. nftables and firewalld on RHEL 9
On RHEL 9, the kernel’s netfilter subsystem is the same for iptables and nftables — both are front ends to the same mechanism. However, Red Hat officially recommends nftables (via the nft command or firewalld) for new deployments because it offers better performance, a cleaner syntax, and atomic rule replacement. The iptables command on RHEL 9 is actually translated to nftables rules under the hood via the iptables-nft backend:
# Check which iptables backend is in use
iptables --version
# Output example: iptables v1.8.9 (nf_tables)
# View the nftables representation of iptables rules
nft list ruleset
If you are starting a new RHEL 9 server, consider using nftables directly or firewalld instead of raw iptables for a more maintainable long-term configuration.
Conclusion
You have configured a functional iptables ruleset on RHEL 9 that drops all unsolicited inbound traffic by default while allowing SSH, HTTP, HTTPS, and ICMP. Rules are persisted via iptables-services and loaded automatically at boot. Understanding iptables fundamentals — tables, chains, match criteria, and targets — gives you a solid foundation for working with any Linux firewall, including the modern nftables that underlies it on RHEL 9.
Next steps: How to Configure nftables Firewall on RHEL 9, How to Audit Linux Security with Lynis on RHEL 9, and How to Configure Fail2Ban on RHEL 9.