The scripts are open source and live on GitHub: Q-Feeds/NFtables-IPtables-integration-script. Always take them from there so you get the current version. You need root access, outbound HTTPS to api.qfeeds.com and a Q-Feeds API token. See Get your threat feeds. The installer asks you to accept our Terms & Conditions and EULA before it does anything.
Prerequisites
- OS: Linux with either nftables or iptables plus ipset
- Distributions: Debian, Ubuntu, CentOS, RHEL, AlmaLinux, Rocky Linux, Fedora, openSUSE Leap, SLES, Arch Linux, Alpine
- Privileges: root for install, updates and firewall changes
- Feed: malware IP list (
feed_type=malware_ip) - Packages: installed for you, so you do not have to prepare anything. For nftables that is
nftables,curl,jqandutil-linux. For iptables it isiptables,ipset,curl,jqandutil-linux.
Optional test before you install:
curl -v -u api_token:YOUR_TOKEN "https://api.qfeeds.com/api?feed_type=malware_ip&limit=1000"
Get the scripts
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
No git on the server? Download the ZIP from the repository page and unpack it instead.
Install
sudo ./qfeeds-installer.sh
The installer detects your firewall backend, asks a handful of questions, installs the updater plus a cron job, and then runs a first full sync so the blocklist is active straight away. The questions, in order:
- Terms and EULA: type
yesto continue. - API token: your Q-Feeds token. The installer stops if you leave this empty.
- Feed type: default
malware_ip. Only change this if we gave you a different feed type. - IP limit: press Enter for no limit, which is what we recommend.
- Block incoming (default yes): drops traffic from blacklisted IPs to your server.
- Block outgoing (default no): drops traffic from your server to blacklisted IPs.
- Whitelist (optional): comma separated IPv4 and IPv6 addresses or CIDRs that must never be blocked. Put your own management IPs here so you cannot lock yourself out.
- Cron schedule: default
*/10 * * * *, so every ten minutes. Running often is fine, because the updater only calls the API when your licence allows a new update.
Warnings about CIDR notation while the feed is being pulled are harmless and can be ignored. If you rerun the installer on a host that already has a Q-Feeds cron entry, it asks before touching your crontab and keeps it unchanged by default. For unattended installs, set QFEEDS_SKIP_CRON=1 to skip that step without a prompt.
How it works
- Backend detection: if the
nftcommand is present the installer uses nftables, otherwise it falls back to iptables with ipset. The result is stored asBACKENDin the config file, and the updater and uninstaller follow it. - Isolated rules: nftables gets its own
ip qfeedsandip6 qfeedstables. On iptables the blocking rules live in dedicatedQFEEDS_INPUTandQFEEDS_OUTPUTchains, with a single tagged jump rule inINPUTandOUTPUT. Your existing firewall rules are never touched. - Two set types: individual addresses go into a hash set and the small number of CIDR ranges into a separate network set. That is what makes loading 400,000 or more addresses take seconds rather than minutes.
- Whitelist first: whitelist rules are evaluated before the blacklist, so a whitelisted address is always accepted.
- Full and diff sync: the first run loads the whole feed, later runs fetch only the additions and removals since your last pull. A full sync is only applied once valid data has been downloaded, so a failed fetch never leaves you unprotected.
- Self healing: if the local set is empty or much smaller than expected, for example after a reboot where rules were not persisted, the updater rebuilds it with a full sync instead of patching an empty set. It also forces a full sync every 24 hours by default.
- Licence based scheduling: before each run the updater checks when your licence allows the next update. If it is not time yet, it exits without calling the API.
Verify
nftables
sudo nft list table ip qfeeds
sudo nft list set ip qfeeds qfeeds_blacklist_v4 | grep -oP '\d+\.\d+\.\d+\.\d+' | wc -l
sudo nft list set ip qfeeds qfeeds_blacklist_v4_nets | head -20
sudo nft list table ip6 qfeeds
iptables with ipset
sudo ipset list -t | grep -A4 qfeeds
sudo ipset list qfeeds_blacklist_v4 | tail -n +9 | wc -l
sudo iptables -L INPUT -n --line-numbers | grep qfeeds
sudo iptables -L QFEEDS_INPUT -n
sudo ip6tables -L QFEEDS_INPUT -n
Both backends
sudo tail -20 /var/log/qfeeds_blocklist.log
sudo grep -i error /var/log/qfeeds_blocklist.log
sudo crontab -l | grep qfeeds
sudo /usr/local/bin/update_qfeeds_blocklist.sh
sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh
The last command forces a full sync and ignores the licence schedule, which is the quickest way to confirm that everything works end to end.
Configuration
All settings live in /etc/qfeeds/qfeeds_config.conf. You can edit that file directly instead of rerunning the installer; changes take effect on the next run.
| Setting | Description | Default |
|---|---|---|
BACKEND | Firewall backend, nftables or iptables | detected |
API_TOKEN | Your Q-Feeds API token | required |
FEED_TYPE | Feed to fetch | malware_ip |
LIMIT | Maximum number of IPs, empty means no limit | empty |
BLOCK_INCOMING | Block traffic from blacklisted IPs | yes |
BLOCK_OUTGOING | Block traffic to blacklisted IPs | no |
WHITELIST_V4 | Comma separated IPv4 addresses or CIDRs | empty |
WHITELIST_V6 | Comma separated IPv6 addresses or CIDRs | empty |
LOG_FILE | Path to the log file | /var/log/qfeeds_blocklist.log |
FULL_SYNC_MAX_AGE | Seconds before a full sync is forced again | 86400 |
File and directory locations
- Configuration:
/etc/qfeeds/qfeeds_config.conf - Sync state:
/etc/qfeeds/.last_syncand/etc/qfeeds/.last_count - Updater script:
/usr/local/bin/update_qfeeds_blocklist.sh - Log:
/var/log/qfeeds_blocklist.log - Lock file:
/var/lock/qfeeds_blocklist.lock - Cron: root crontab
- Persistence:
netfilter-persistentwhen available, otherwise/etc/nftables.conffor nftables or/etc/ipset.confwith/etc/iptables.rulesand/etc/ip6tables.rulesfor iptables
Uninstall
sudo ./qfeeds-uninstaller.sh
The uninstaller reads the configured backend and cleans up accordingly. On nftables it deletes the ip qfeeds and ip6 qfeeds tables with all their chains, rules and sets. On iptables it removes the tagged jump rules, flushes and deletes the QFEEDS_INPUT and QFEEDS_OUTPUT chains, and destroys every qfeeds_ set. In both cases it also removes the config directory, the updater script, the cron job, the log and the lock file. If the config file is gone it cleans up both backends to be safe.
System packages are left in place. Remove them yourself if you no longer need them:
- Ubuntu/Debian:
sudo apt-get -y remove ipset netfilter-persistent - CentOS/RHEL/Alma/Rocky:
sudo yum -y remove ipset - Fedora:
sudo dnf -y remove ipset - openSUSE/SLES:
sudo zypper remove ipset - Arch:
sudo pacman -R ipset
Be careful with iptables, nftables, curl and jq: other parts of your system almost certainly need them.
Troubleshooting
- Sets are empty after installing: check
sudo tail -50 /var/log/qfeeds_blocklist.log, confirm your API token, then runsudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh. - "Not time yet. Next update scheduled at ...": this is normal. The cron job ran, but your licence does not allow a new update yet. The next run checks again.
- Rules gone after a reboot: the updater saves rules automatically, and on the next run it notices the empty set and rebuilds it with a full sync. To be sure, check that
netfilter-persistentis installed or that thenftablesservice is enabled. - "Batch nft -f failed. Falling back to per-command execution": harmless. It usually happens in LXC containers with a restricted netlink buffer. The fallback still loads 400,000 or more addresses in roughly ten seconds. The same goes for "Message too long".
- "ipset restore failed": confirm ipset is present with
command -v ipsetand that the kernel module is loaded withlsmod | grep ip_set. - No rules visible in INPUT: on iptables the blocking rules sit in
QFEEDS_INPUT.INPUTonly holds the jump rule, tagged with the commentqfeeds. Check both withsudo iptables -L INPUT -n | grep qfeedsandsudo iptables -L QFEEDS_INPUT -n. - Installation fails with "Unable to locate package": distribution detection did not work. Install
curl,jqandutil-linuxby hand, plusnftablesoriptableswithipset, then run the installer again. - Support: support@qfeeds.com or the support page. Found a bug in the scripts? Open an issue in the GitHub repository.