Add a free attacker blocklist to pfSense (pfBlockerNG), OPNsense and MikroTik
Add a free list of confirmed attacker IPs to pfBlockerNG, an OPNsense URL table alias or a MikroTik address list, with tested settings and a safe refresh.
To feed pfBlockerNG a free attacker blocklist, add an IPv4 list with the source https://ffraud.com/free/badips.txt, the format Auto and the action Deny Inbound. Set it to update every 6 hours. OPNsense loads the same address as a URL Table (IPs) alias, blocked by one rule on WAN. On MikroTik, a short script fills an address list, and one raw rule drops it.
The list holds 2,000 IPs confirmed abusive six or more times in the last 14 days. FFraud rebuilds it every six hours and asks for no account or key.
#Which free list should a firewall load?
FFraud publishes four plain-text lists, one IP per line, with the strongest entries first. Each file states its rule in a comment at the top.
| List | What earns a listing | IPs |
|---|---|---|
| Confirmed abusive 6 or more times in 14 days | 2,000 | |
| Confirmed abusive 10 or more times in 14 days | 500 | |
| Seen attacking web apps, confirmed 4 or more times | 2,000 | |
| First seen in the last 7 days, confirmed 3 or more times | 2,500 |
Start with badips.txt on a firewall that guards a whole network. worst.txt is the strictest of the four. webattack.txt suits a network that runs web servers. freships.txt catches new attackers on fewer confirmations.
On October 25, 2026, 184 of the 2,000 IPs in badips.txt were Tor exits on the Tor Project's own list. Loading it also blocks new connections from those exits.
#How do you add the list to pfBlockerNG?
pfBlockerNG puts each feed into an alias and writes the block rule for you (Netgate documentation). The steps follow the forms of pfBlockerNG 3.2.15, the version in pfSense's package source on October 25, 2026.
- Under Firewall > pfBlockerNG > General, tick Enable. On the IP tab, select WAN under Inbound Firewall Rules.
- Open IP > IPv4 and add a list. Name it
FFraud_badips, because the name takes no spaces or special characters. - In the source row, choose the format Auto and the state ON. Paste
https://ffraud.com/free/badips.txtas the Source andFFraud_badipsas the Header/Label. - Set Action to Deny Inbound and Update Frequency to Every 6 hours, then save.
- On the Update tab, choose Reload and IP, then run it to load the list now.
The update frequency must fit the global CRON settings on the General tab, which run every hour at minute 00 by default. The block rule goes ahead of your other rules, unless you change pfBlockerNG's rule order.
How pfBlockerNG reads each line
pfBlockerNG skips every line that starts with #, so the comment header never reaches the firewall. A feed whose address ends in .txt goes through its Auto parser, one IP per line. That parser has one rule to know: a bare IP whose last number is 0 becomes a whole /24 network.
We ran all four lists through a copy of that parser on October 25. badips.txt and worst.txt loaded as single IPs. webattack.txt held 1 IP that ends in .0 and freships.txt held 15, and pfBlockerNG reads each of them as 256 addresses. An IPv4 list also skips IPv6 lines, so the 27 IPv6 entries of freships.txt need an IPv6 list of their own.
#How do you add the list to OPNsense?
- Go to Firewall > Aliases, add an alias named
ffraud_badipsand choose the type URL Table (IPs). - Enter
https://ffraud.com/free/badips.txtas the content and set Refresh Frequency to 0 days and 6 hours. Save and apply. - Under Firewall > Rules, on the WAN interface, add a rule with the action Block, the IP version IPv4 and the source
ffraud_badips. - Move the rule above every WAN rule that passes traffic. OPNsense rules are first match by default, so a pass rule above it wins.
- Check what loaded under Firewall > Diagnostics > Aliases.
OPNsense's own guide for the Spamhaus DROP list builds the same alias and WAN rule. Its manual asks for one IPv4 or IPv6 address per line and ignores lines that start with #, which is how FFraud writes its lists.
One trap is worth knowing. OPNsense treats any line that is not an address as a host name and tries to resolve it. Our test found no such line in the four lists. For the IPv6 entries of freships.txt, add a second rule with the IP version IPv6.
#How do you load the list on MikroTik?
RouterOS fills an address list in three ways: you add entries, firewall rules add them, or the router resolves a DNS name (RouterOS manual). None of them reads a list from a URL. The recipe uses a script that downloads the list with /tool fetch and adds each IP. A raw rule then drops the list before connection tracking.
The script is written for RouterOS 7.19 or later, because check-certificate=yes uses the root certificates that 7.19 added. Add the drop rule once:
/ip/firewall/raw/add chain=prerouting action=drop in-interface-list=WAN \
src-address-list=ffraud-a comment="ffraud blocklist"Then add the script. It waits for the default route, loads the list that the rule does not use, points the rule at it and empties the old one:
/system/script/add name=ffraud-blocklist source={
:local ffUrl "https://ffraud.com/free/badips.txt"
:local ffTries 0
:while (([:len [/ip/route/find where dst-address=0.0.0.0/0 active]] = 0) && ($ffTries < 60)) do={
:delay 5s
:set ffTries ($ffTries + 1)
}
:local ffRule [/ip/firewall/raw/find where comment="ffraud blocklist"]
:if ([:len $ffRule] != 1) do={ :error "ffraud: add the raw rule first" }
:local ffOld [/ip/firewall/raw/get $ffRule src-address-list]
:local ffNew "ffraud-a"
:if ($ffOld = "ffraud-a") do={ :set ffNew "ffraud-b" }
:local ffData ""
:do {
:set ffData ([/tool/fetch url=$ffUrl check-certificate=yes output=user as-value]->"data")
} on-error={
:log warning "ffraud: download failed, list left as it is"
:error "ffraud: download failed"
}
:local ffSize [:len $ffData]
:if ($ffSize >= 64512) do={
:log warning "ffraud: download reached the fetch limit, list left as it is"
:error "ffraud: download cut"
}
/ip/firewall/address-list/remove [find where list=$ffNew]
:local ffCount 0
:local ffStart 0
:local ffEnd 0
:while ($ffStart < $ffSize) do={
:set ffEnd [:find $ffData "\n" $ffStart]
:if ([:len $ffEnd] = 0) do={ :set ffEnd $ffSize }
:local ffLine [:pick $ffData $ffStart $ffEnd]
:set ffStart ($ffEnd + 1)
:if ([:typeof [:toip $ffLine]] = "ip") do={
:do {
/ip/firewall/address-list/add list=$ffNew address=$ffLine timeout=1w
:set ffCount ($ffCount + 1)
} on-error={}
}
}
:if ($ffCount < 100) do={
/ip/firewall/address-list/remove [find where list=$ffNew]
:log warning "ffraud: only $ffCount addresses in the download, list left as it is"
:error "ffraud: short list"
}
/ip/firewall/raw/set $ffRule src-address-list=$ffNew
/ip/firewall/address-list/remove [find where list=$ffOld]
:log info "ffraud: $ffCount addresses loaded into $ffNew"
}Run it at boot and every 6 hours. A startup entry with an interval does not run at boot itself, so it takes two entries (RouterOS manual):
/system/scheduler/add name=ffraud-blocklist-boot start-time=startup on-event=ffraud-blocklist
/system/scheduler/add name=ffraud-blocklist-6h interval=6h on-event=ffraud-blocklist- Two lists. The rule switches only once the new list is full, so traffic always meets a whole list, never half of one.
- The size limit. With
output=user,/tool fetchkeeps at most 64,512 bytes and cuts a longer body without an error. The script refuses a download that reaches the limit. - Every line is checked. RouterOS stores text that is not an address as a DNS name, without an error. The script adds only lines that
:toipreads as an IPv4 address, so comments and IPv6 lines stay out. - Reboots. Entries with a timeout live in memory, and a reboot clears them. The boot entry loads the list again once the default route is up.
- Names. Each variable starts with
ff. A variable named like a property, such aslist, is misread inside afind(RouterOS manual).
- worst.txt7,361 bytes
- badips.txt28,752 bytes
- webattack.txt29,362 bytes
- freships.txt36,058 bytes
- RouterOS limit64,512 bytes
Source: FFraud free lists, read October 25, 2026; RouterOS manual, Fetch
We wrote the script from the RouterOS 7.26 manual and checked the downloads against every limit it enforces. It is untested on a real router, so run it once with /system/script/run ffraud-blocklist. Then read the log and the list before you trust the schedule. The scheduler is not available when the device mode is home.
#What happens when the list cannot be downloaded?
All three setups keep the last good list when a download fails, and none of them blocks all traffic. That suits a blocklist, which is one layer of defense: a feed outage should never cut a network off.
| Setup | When a download fails | Where it says so |
|---|---|---|
| pfBlockerNG | Logs Download FAIL and restores the last downloaded file | pfblockerng.inc |
| OPNsense | Logs the error and rolls back to the last loaded IPs | Alias code |
| MikroTik script | Logs a warning and stops before it touches a list | The script above |
A first download that fails leaves the list empty on all three, so traffic passes until a download works. After a reboot, the MikroTik lists start empty for the same reason. Each setup also gives up on a slow server. pfBlockerNG waits 15 seconds for a connection, OPNsense allows 120 seconds, and RouterOS stops after 10 seconds without data.
#How often should the firewall refresh the list?
FFraud rebuilds the four lists every six hours, so a refresh every 6 hours picks up each new build. A shorter interval downloads the same file again. The lists are served with Cache-Control: public, max-age=1800, so a proxy between you and FFraud may hand out a copy up to 30 minutes old.
#How do you check that it works?
Count the loaded IPs first. pfBlockerNG names its table pfB_ plus the list name plus _v4, and OPNsense names the table after the alias:
# pfSense with pfBlockerNG
pfctl -t pfB_FFraud_badips_v4 -T show | wc -l
# OPNsense
pfctl -t ffraud_badips -T show | wc -l/ip/firewall/address-list/print count-only where list~"^ffraud-"Then ask the free API about one listed IP. It needs no key and says why the address is on the list:
curl https://api.ffraud.com/public/ip/157.66.26.151Our test fed the four lists to copies of the pfBlockerNG and OPNsense line parsers and to the script's line check. The fields are explained in the
list IPs pfBlockerNG as /24 IPv6 skipped OPNsense MikroTik script bytes
badips.txt 2000 2000 0 0 2000 2000 28752
webattack.txt 2000 2000 1 0 2000 2000 29362
worst.txt 500 500 0 0 500 500 7361
freships.txt 2500 2473 15 27 2500 2473 36058
every file: line feeds only, under the 64512-byte fetch limit,
HTTP 200 for the user agents of pfBlockerNG, OPNsense and RouterOS#Can you load the full 1.6 million list instead?
FFraud's
pfSense allows 400,000 table entries by default. Netgate asks for at least double the entries of all aliases, because the firewall loads a new list beside the old one. It counts about 1 KB of memory per entry to be safe. OPNsense starts at 1,000,000 entries and raises its default with memory.
In pfBlockerNG, the 3,670 rows that end in .0 would each block a /24. A MikroTik script cannot fetch the file at all, since it is far above the 64,512-byte limit. For most networks, a capped list of 2,000 is the better trade. The
#Questions people ask
Which IP blocklists should I use in pfBlockerNG?
Start with one list of recent attackers, such as badips.txt. OPNsense's own guide adds Spamhaus DROP, a list of networks hijacked or leased by spam and crime operations. Each extra list raises the chance of a false positive, so add them one at a time. Our
Why does pfBlockerNG say it cannot allocate memory when it loads lists?
One known cause is the table limit. When Firewall Maximum Table Entries is too small for all aliases, Netgate warns that the firewall may fail to load the ruleset. Raise it under System > Advanced > Firewall & NAT to at least double the entries of all aliases.
How do I import an IP list from a file into a MikroTik address list?
For a one-time load, a file of add commands and /import works, but the import stops at the first IP already in the list (RouterOS manual). For a list that changes, use a script that fetches the file and adds each line, as above.
Is there a size limit when MikroTik fetches a list into a variable?
Yes. With output=user, /tool fetch keeps at most 64,512 bytes and cuts the rest without an error. Compare the length of the data with that limit to catch a cut list.
How do I use a URL table alias as a blocklist in OPNsense?
Create a URL Table (IPs) alias with the list's address and a refresh frequency. Then use it as the source of a block rule on WAN. Firewall > Diagnostics > Aliases shows what it loaded.
Should I paste IPs into the pfBlockerNG custom list or use a feed URL?
Use a feed URL for any list that changes. A custom list lives inside the firewall's config.xml file and changes only when you edit it.
To see why one IP landed on the list, paste it into the free
Sources
pfBlocker-NG Package Firewall & NAT: Firewall Maximum Table Entries pfSense-pkg-pfBlockerNG 3.2.15: pfblockerng.inc and the settings forms Aliases: URL Tables Configure Spamhaus DROP Rules: processing order Alias parsing and loading (lib/alias, update_tables.py, filter.inc) Fetch Address lists Scheduler Scripting What's new in 7.19 Tor bulk exit list Free lists badips, webattack, worst and freships, build of 18:17 UTC