Mastering Firewalld IP Sets on CentOS Stream 9: Block Thousands of IPs Instantly

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

The Problem with Manual IP Blocking and the Power of IP Sets

When I first started managing large web systems, I often faced scenarios where servers were hit by brute-force attacks from thousands of IPs. Back then, I painstakingly typed firewall-cmd --add-rich-rule to block each address individually. The result was an endless rule table. Every time the firewall reloaded, the server’s CPU spiked to 100%, causing serious bottlenecks for legitimate user requests.

After that incident, I learned a valuable lesson: never manage hundreds of IPs using individual rules. That’s where IP Sets come in as the optimal solution. This tool allows you to group thousands of IPs or subnets into a single list. Instead of scanning through 1,000 rules to check a single packet, the firewall only needs to check once thanks to the Hash Table structure. The processing speed becomes O(1), which is nearly instantaneous.

Although CentOS Stream 9 has moved to nftables as the backend, Firewalld IP Sets remain extremely powerful. If you want to thoroughly block traffic from countries that frequently launch attacks, this is a technique you cannot afford to skip.

Installation and Environment Preparation

CentOS Stream 9 usually comes with Firewalld pre-installed, but we need to ensure the service is running stably. Let’s install the ipset package to support quick inspection operations.

# Quick system update
sudo dnf update -y

# Install necessary tools
sudo dnf install firewalld ipset -y

# Enable firewalld
sudo systemctl enable --now firewalld

# Check operating status
sudo firewall-cmd --state

When migrating systems from CentOS 7 to version 9, I noticed that the command structure hasn’t changed much. However, IP Set processing performance on version 9 is significantly smoother due to the underlying nftables optimization.

Detailed Configuration of IP Sets on Firewalld

Step 1: Create a New IP Set

First, create a “bucket” to hold the blacklist. I’ll name it blacklist_ips. The most common IP Set type is hash:net, which supports both individual IPs and CIDR subnets.

sudo firewall-cmd --permanent --new-ipset=blacklist_ips --type=hash:net

Parameter details:

  • --permanent: Saves the configuration permanently across reboots.
  • --new-ipset: Names the list.
  • --type=hash:net: Data type for storing network ranges (e.g., 192.168.1.0/24).

Step 2: Adding IPs to the List

You can manually add suspicious IPs or subnets that frequently spam to the newly created set.

# Block a specific IP
sudo firewall-cmd --permanent --ipset=blacklist_ips --add-entry=1.2.3.4

# Block an entire subnet
sudo firewall-cmd --permanent --ipset=blacklist_ips --add-entry=192.168.100.0/24

If your list grows to hundreds of IPs, don’t type them manually. Save them to a list.txt file (one IP per line) and use a bash loop to import them:

for ip in $(cat list.txt); do 
    sudo firewall-cmd --permanent --ipset=blacklist_ips --add-entry=$ip
done

Step 3: Activating the Blocking Rule

After creating the list, the firewall still won’t do anything. You need to add a “rich rule” to command Firewalld: “If any IP is found in blacklist_ips, DROP it immediately.”

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="blacklist_ips" drop'

Finally, reload for all changes to take effect:

sudo firewall-cmd --reload

How to Blacklist All IPs from a Specific Country

This is an extremely effective tactic. Suppose your server only serves users in Vietnam. Blocking traffic from unrelated countries can reduce the risk of automated vulnerability scans by 80-90%.

Using data from IPDeny, you can automate this process with a script:

# Create an IP Set for a specific country (e.g., China - code 'cn')
sudo firewall-cmd --permanent --new-ipset=country_block --type=hash:net

# Download the latest IP range list
curl -O http://www.ipdeny.com/ipblocks/data/countries/cn.zone

# Import thousands of subnets into the Set
for range in $(cat cn.zone); do
    sudo firewall-cmd --permanent --ipset=country_block --add-entry=$range
done

# Apply the DROP rule
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="country_block" drop'
sudo firewall-cmd --reload

Note: The process of importing 5,000 – 8,000 subnets may take 1-2 minutes. However, once loaded, packet filtering will not cause any lag for the server.

Testing and Monitoring

To ensure the system is running correctly, use the following check commands:

1. List existing IP Sets:

sudo firewall-cmd --get-ipsets

2. View the IP list inside a Set:

sudo firewall-cmd --ipset=blacklist_ips --get-entries

3. Quickly check if an IP is blocked:

ipset test blacklist_ips 1.2.3.4

Professional Management Tips

  • Use Drop instead of Reject: When blocking on a large scale, use drop to silently terminate connections. reject sends a response back to the attacker, wasting bandwidth unnecessarily.
  • Backup configuration: All IP Sets are stored as XML files at /etc/firewalld/ipsets/. You just need to copy these files to a new server.
  • Automatic updates: Country IP ranges change constantly. You should set up a weekly Cronjob to redownload the zone files and update the list automatically.

Mastering IP Sets has made me much more confident when facing attacks from the internet. With just a few lines of code, you have set up a solid “shield” for your CentOS Stream 9 server without worrying about sacrificing performance.

Share: