
Script to implement Q-Feeds directly on NFtables or IPtables
Automated malware IP blocklist for Linux servers — supports nftables and iptables+ipset
Obtain a free API key at tip.qfeeds.com.
git clone https://github.com/Q-Feeds/NFtables-IPtables-integration-script.git
cd NFtables-IPtables-integration-script
chmod +x qfeeds-installer.sh qfeeds-uninstaller.sh
sudo ./qfeeds-installer.sh
The installer will:
Your server is now protected. The cron job checks for updates every 20 minutes (configurable), and actual API calls only happen when your license allows.
This solution periodically downloads the latest threat intelligence feed from Q-Feeds and applies it as firewall rules, allowing you to:
The installer automatically detects which firewall backend is available:
| Priority | Detection | Backend |
|---|---|---|
| 1st | nft command found | nftables |
| 2nd | iptables command found | iptables+ipset |
| — | Neither found | Error (exit) |
The detected backend is stored in the configuration file. The updater and uninstaller scripts use it to run the correct firewall commands.
Both backends use the same split-set strategy for maximum performance:
nftables backend:
┌─────────────────────────────────────────────────────────┐
│ table ip qfeeds │
│ │
│ ┌─────────────────────────┐ ┌───────────────────────┐ │
│ │ qfeeds_blacklist_v4 │ │ qfeeds_blacklist_v4 │ │
│ │ (hash set) │ │ _nets (interval set) │ │
│ │ │ │ │ │
│ │ Individual IPs │ │ CIDR ranges │ │
│ │ ~99% of entries │ │ ~1% of entries │ │
│ │ O(1) lookup & insert │ │ O(log n) lookup │ │
│ └─────────────────────────┘ └───────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ qfeeds_whitelist_v4 │ │
│ │ (interval set) │ │
│ │ Your allowed IPs/CIDRs │ │
│ └─────────────────────────┘ │
│ │
│ chain input-chain (hook input, priority 0, accept) │
│ → ip saddr @qfeeds_whitelist_v4 accept │
│ → ip saddr @qfeeds_blacklist_v4 drop │
│ → ip saddr @qfeeds_blacklist_v4_nets drop │
│ │
│ chain output-chain (if enabled) │
│ → ip daddr @qfeeds_whitelist_v4 accept │
│ → ip daddr @qfeeds_blacklist_v4 drop │
│ → ip daddr @qfeeds_blacklist_v4_nets drop │
└─────────────────────────────────────────────────────────┘
iptables+ipset backend:
┌──────────────────────────────────────────────────────────┐
│ ipset sets │
│ │
│ ┌─────────────────────────┐ ┌────────────────────────┐ │
│ │ qfeeds_blacklist_v4 │ │ qfeeds_blacklist_v4 │ │
│ │ (hash:ip) │ │ _nets (hash:net) │ │
│ │ maxelem 1000000 │ │ maxelem 65536 │ │
│ │ │ │ │ │
│ │ Individual IPs │ │ CIDR ranges │ │
│ └─────────────────────────┘ └────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ qfeeds_whitelist_v4 │ │
│ │ (hash:net) │ │
│ └─────────────────────────┘ │
│ │
│ iptables: INPUT/OUTPUT jump to a dedicated chain │
│ (jump rule tagged -m comment "qfeeds"): │
│ │
│ chain QFEEDS_INPUT (rebuilt each run, in order): │
│ -m set --match-set whitelist_v4 src -j ACCEPT │
│ -m set --match-set blacklist_v4 src -j DROP │
│ -m set --match-set blacklist_v4_nets src -j DROP │
│ (QFEEDS_OUTPUT mirrors this with dst, if enabled) │
└──────────────────────────────────────────────────────────┘
The same structure exists for IPv6 (ip6 qfeeds table or ip6tables + family inet6 ipsets).
Why two set types?
┌──────────────────────────────────────────────────────┐
│ 1. Check license schedule (licenses.php API) │
│ → Skip run if not yet time for next update │
│ 2. Determine sync mode (full or diff) │
│ 3. Fetch IPv4 feed (ipv6=0) and IPv6 feed │
│ (ipv6=only) separately │
│ 4. Separate IPs from CIDRs in awk │
│ 5. Batch-load into hash set (IPs) and net/interval │
│ set (CIDRs) │
│ 6. Update whitelist sets from config │
│ 7. Persist rules │
└──────────────────────────────────────────────────────┘