iptables and ipset can block thousands of IPs efficiently by moving the IP lookup from the kernel’s packet filtering rules into a dedicated kernel data structure.

Here’s how it works in practice:

Let’s say you have a list of IPs you want to block, perhaps from a brute-force attack. Traditionally, you might add a rule for each IP:

sudo iptables -A INPUT -s 1.2.3.4 -j DROP
sudo iptables -A INPUT -s 5.6.7.8 -j DROP
# ... and so on for thousands of IPs

This quickly becomes unmanageable and, more importantly, inefficient. Each iptables rule requires the kernel to traverse a linked list of rules for every incoming packet. With thousands of rules, this can significantly impact your firewall’s performance.

ipset solves this by creating a kernel-level hash table (or other data structures) to store these IP addresses. Instead of thousands of individual iptables rules, you have one iptables rule that checks if an IP address exists in the ipset.

First, create an ipset list. A common type is hash:net for storing network ranges or individual IPs.

sudo ipset create blocked_ips hash:net

Now, add your IP addresses to this set. You can add individual IPs or entire subnets.

sudo ipset add blocked_ips 1.2.3.4
sudo ipset add blocked_ips 5.6.7.8
sudo ipset add blocked_ips 192.168.1.0/24

You can add IPs from a file like this:

while read ip; do sudo ipset add blocked_ips "$ip"; done < ip_list.txt

Once your ipset is populated, you create a single iptables rule that references it. This rule should be placed before any general accept rules.

sudo iptables -I INPUT -m set --match-set blocked_ips src -j DROP

Let’s break down that iptables rule:

  • -I INPUT: Insert this rule at the beginning of the INPUT chain. This is crucial for efficiency; you want to drop malicious traffic as early as possible.
  • -m set: Load the set match module. This is what allows iptables to interact with ipset.
  • --match-set blocked_ips src: This is the core of the rule. It tells iptables to check if the source IP address (src) of the incoming packet is present in the ipset named blocked_ips.
  • -j DROP: If the source IP is found in the blocked_ips set, the packet is dropped immediately.

The performance difference is dramatic. Instead of traversing thousands of rules, the kernel performs a quick hash lookup in the ipset data structure, which is O(1) on average. This makes blocking vast numbers of IPs feasible without a significant performance hit.

To view the contents of your ipset:

sudo ipset list blocked_ips

To remove an IP:

sudo ipset del blocked_ips 1.2.3.4

To destroy the ipset entirely:

sudo ipset destroy blocked_ips

When dealing with large numbers of IPs, you’ll often combine ipset with iptables-persistent or similar tools to ensure your rules and sets survive reboots. For iptables-persistent, you’d typically save the ipset state and then save the iptables rules.

sudo ipset save > /etc/ipset.conf
sudo iptables-save > /etc/iptables/rules.v4

Then, configure your system to restore these on boot. This often involves systemd services or init scripts that run ipset restore < /etc/ipset.conf and iptables-restore < /etc/iptables/rules.v4.

A common pitfall is placing the iptables rule too late in the chain. If you have a broad ACCEPT rule before your DROP rule referencing the ipset, the packet might be accepted before the ipset check even occurs. Always insert your ipset match rule at the top of relevant chains.

If you find yourself needing to block based on destination IP, port, or even MAC address along with the IP, you can use different ipset types like hash:ip,port or create multiple ipsets and chain iptables rules.

The next challenge you’ll likely encounter is managing the dynamic updating of these IP sets, perhaps by integrating them with threat intelligence feeds or intrusion detection systems.

Want structured learning?

Take the full Iptables course →