When you need to restrict access to specific geographic regions at the network layer, iptables provides a robust solution. For US-only access control, we'll leverage CIDR notation blocks covering all US IP ranges. This method is more efficient than individual IP filtering and operates at the kernel level.
First, obtain an updated list of US IP ranges in CIDR notation. You can find these from organizations like ARIN or use curated lists from services like ip2location. Here's a sample of what your IP block file might contain:
# US IP blocks (sample) 3.0.0.0/8 4.0.0.0/8 8.0.0.0/8 ...
We'll create a whitelist approach where we first block all traffic, then allow only US IP blocks. Here's the basic structure:
#!/bin/bash # Flush existing rules iptables -F # Set default policies iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # Allow localhost iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT # Allow established connections iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Now we'll process the CIDR file and create allow rules for each block:
# Read US IP blocks from file and create rules while read -r cidr; do iptables -A INPUT -s "$cidr" -j ACCEPT done < us_ip_blocks.txt # Optional: Log rejected attempts iptables -A INPUT -j LOG --log-prefix "IPTABLES-BLOCKED: "
For better performance with large IP block lists:
# Create a custom chain for US IP filtering iptables -N US_FILTER iptables -A INPUT -j US_FILTER # Add rules to the custom chain while read -r cidr; do iptables -A US_FILTER -s "$cidr" -j RETURN done < us_ip_blocks.txt # Drop non-matching traffic iptables -A US_FILTER -j DROP
To make your rules persistent across reboots on Debian/Ubuntu systems:
# Install persistence package apt-get install iptables-persistent # Save current rules netfilter-persistent save
Verify your rules with:
iptables -L -n -v
And check your logs for blocked attempts:
tail -f /var/log/syslog | grep IPTABLES-BLOCKED
When restricting web services to specific geographic regions, IP-based filtering remains the most reliable method at the network layer. The United States has over 20,000 registered IP blocks (as of ARIN's 2023 data), making manual entry impractical. Here's how to implement this efficiently:
First, acquire an authoritative US IP range list. I recommend using ARIN's delegated data:
wget https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
grep "US|ipv4" delegated-arin-extended-latest | awk -F'|' '{print $4 "/" 32-log($5)/log(2)}' > us-cidr.txt
The naive approach would create thousands of individual rules, causing performance issues. Instead, use ipset for efficient matching:
# Create ipset hash
ipset create us-ips hash:net
# Populate from CIDR file
while read cidr; do
ipset add us-ips $cidr
done < us-cidr.txt
With the ipset prepared, create iptables rules that reference it:
# Basic rule structure
iptables -A INPUT -p tcp --dport 80 -m set ! --match-set us-ips src -j DROP
iptables -A INPUT -p tcp --dport 443 -m set ! --match-set us-ips src -j DROP
# Alternative logging version for debugging
iptables -N US_FILTER
iptables -A US_FILTER -m set ! --match-set us-ips src -j LOG --log-prefix "Non-US Access: "
iptables -A US_FILTER -j DROP
iptables -A INPUT -p tcp --dport 80 -j US_FILTER
iptables -A INPUT -p tcp --dport 443 -j US_FILTER
For high-traffic servers, these optimizations help:
- Place the rules early in your iptables chain
- Consider using nftables instead for better performance with large sets
- Monitor CPU usage during peak traffic
IP allocations change frequently. Set up a monthly cron job:
0 3 1 * * /usr/sbin/ipset flush us-ips && \
wget -qO- https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest | \
grep "US|ipv4" | awk -F'|' '{print $4 "/" 32-log($5)/log(2)}' | \
while read cidr; do ipset add us-ips $cidr; done