Although firewalld ships as the default firewall manager on RHEL 8, some administrators prefer the well-known iptables command syntax for its long history, broad documentation, and deterministic rule ordering. RHEL 8 supports running iptables as a standalone service through the iptables-services package, which must be installed after disabling firewalld to avoid conflicts. This tutorial covers switching from firewalld to iptables, building a baseline rule set that allows SSH and established connections, setting a default DROP policy, persisting rules across reboots, and extending the configuration to IPv6 with ip6tables.
Prerequisites
- RHEL 8 server with root or sudo access
- An active SSH session — keep a console fallback open before changing firewall rules
- Basic understanding of TCP/IP ports and network protocols
- Knowledge of which services your server must expose before applying a DROP policy
Step 1 — Disable firewalld and Install iptables-services
Stop and permanently disable firewalld to prevent it from interfering with iptables rules, then install the iptables services package.
sudo systemctl disable --now firewalld
sudo systemctl mask firewalld
sudo dnf install -y iptables-services
sudo systemctl enable --now iptables
sudo systemctl enable --now ip6tables
Masking firewalld prevents other packages from accidentally starting it again during system updates.
Step 2 — View the Current iptables Rule Set
After installation the iptables service loads its default rules from /etc/sysconfig/iptables. List all current rules in verbose mode to understand the baseline state.
sudo iptables -L -v -n --line-numbers
By default the iptables-services package installs an open policy (ACCEPT for INPUT, OUTPUT, and FORWARD), meaning all traffic passes through until you define a DROP policy. Do not add the DROP rule until all necessary ACCEPT rules are in place.
Step 3 — Build a Baseline Rule Set
Flush any existing rules and add a safe baseline. Work in this exact order to avoid accidentally blocking your own SSH connection.
# Flush all current rules (ACCEPT policy must be in place first)
sudo iptables -F
sudo iptables -X
# Allow loopback interface traffic
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
# Allow established and related connections (essential — add before DROP)
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (port 22) from any source — restrict source IP in production
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP and HTTPS if this is a web server
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow ICMP ping (optional but useful for monitoring)
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
Step 4 — Set the Default DROP Policy
Once all required ACCEPT rules are in place, set the INPUT and FORWARD chains to DROP. OUTPUT remains ACCEPT so the server can initiate outbound connections (DNS, updates, etc.).
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
Verify your SSH session is still active. Then confirm the rule set looks correct.
sudo iptables -L -v -n --line-numbers
Step 5 — Save Rules to Persist Across Reboots
The iptables-services package provides a service iptables save command that writes the live rule set to /etc/sysconfig/iptables so they are restored on every boot.
sudo service iptables save
# Confirm the file was written
cat /etc/sysconfig/iptables
To restore rules manually at any time without rebooting, run sudo service iptables restart or sudo iptables-restore < /etc/sysconfig/iptables.
Step 6 — Configure ip6tables for IPv6
If your server has an IPv6 address, mirror the IPv4 rules for ip6tables. IPv4 and IPv6 rule sets are managed independently.
sudo ip6tables -F
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT ACCEPT
# Save IPv6 rules
sudo service ip6tables save
Note that ICMPv6 (ipv6-icmp) should always be ACCEPTed rather than DROPped; many IPv6 features such as neighbour discovery depend on it.
Conclusion
You have disabled firewalld on RHEL 8, installed the iptables-services package, built a baseline rule set with explicit ACCEPT rules for SSH and web traffic, applied a default DROP policy on the INPUT and FORWARD chains, persisted the rules to survive reboots, and replicated the configuration for IPv6 with ip6tables. This deterministic rule model gives you full control over inbound traffic with no zones or service abstractions in between.
Next steps: Configure nftables Firewall on RHEL 8, Restrict SSH Access by IP with AllowUsers and TCP Wrappers on RHEL 8, and Audit Your Firewall Configuration with Lynis on RHEL 8.