- Home
- Integrations
- pfSense
Setup guide
pfSense malicious IP blocklist through pfBlockerNG or a URL Table alias
No credit card required · Free API key
On this page08
What you get
pfSense keeps IP lists in pf tables. Firewall Maximum Table Entries is 400,000 by default, and about 160,000 of them already hold the bogon tables when IPv6 is allowed and Block bogon networks is on, both factory defaults. Netgate requires room for twice the total of all tables, so raise it to at least 600,000 before loading the critical IPs. The domain lists go to pfBlockerNG’s DNSBL.
| List | Entries | Rebuilt | Plans |
|---|---|---|---|
| blocklist-ips-critical.txtDefault. IPs listed by 6 or more threat sources, or 3 or more with a critical category: a URL Table alias, or pfBlockerNG IPv4 and IPv6. | about 89,000 | every 12 h | Basic, Pro, and Enterprise |
| blocklist-ips-c2.txtCommand-and-control IPs reported by C2 trackers: a smaller IP list. | about 44,000 | every 12 h | Basic, Pro, and Enterprise |
| blocklist-domains-c2.txtpfBlockerNG DNSBL: command-and-control domains reported by C2 trackers. | about 22,000 | every 12 h | Basic, Pro, and Enterprise |
| blocklist-domains-ransomware.txtpfBlockerNG DNSBL: domains in the ransomware category, whatever their level. | about 3,600 | every 12 h | Basic, Pro, and Enterprise |
| blocklist-domains-cryptomining.txtpfBlockerNG DNSBL: domains in the cryptomining category, whatever their level. | about 6,100 | every 12 h | Basic, Pro, and Enterprise |
Entries rounded from the build of ; each list is rebuilt every 12 hours. Today’s counts are public and need no key.
What each plan receives
- FreeFree account, or no key: the first 10% of each list, marked
X-Blocklist-Version: lite. - Basic, Pro, and EnterpriseBasic, Pro, and Enterprise: every list in full.
- Pro and EnterpriseTAXII 2.1 collections, for platforms that read STIX indicators: Pro and Enterprise.
GET https:/
Full list or 10% sample
| Field | Basic, Pro, and Enterprise | Free |
|---|---|---|
X-Blocklist-Version: | full | lite |
X-Blocklist-Percentage: | 100 | 10 |
Total entries: | <COUNT> | <COUNT> |
First lines of the file
# IsMalicious.com Blocklist - IPs (Critical)
# Format: Plain
# Generated: <BUILD_TIME>
# Total entries: <COUNT>
# Update frequency: every 12 hours
# Category: All
# Threat level: Critical
# Website: https://ismalicious.com
# © <YEAR> IsMalicious (compilation). Licensed to the downloading account under https://ismalicious.com/terms; redistribution of the compilation prohibited. Third-party entries remain under their providers' licences — see https://ismalicious.com/sources.
#Values in angle brackets are set by each build.
Prerequisites
- A Linux host with systemd, curl and an HTTPS web server, which pfSense reaches on your network.
- pfSense CE or pfSense Plus. For an hourly refresh, IPv6 tabs and DNSBL, the pfBlockerNG or pfBlockerNG-devel package (menus checked against 3.2.15_2 and 3.2.16).
- For DNSBL: the DNS Resolver (Unbound) enabled, and clients that use pfSense as their DNS server.
- An isMalicious API key and secret, from Account › API access.
- Outbound HTTPS (TCP 443) over IPv4 from the relay host to
api.ismalicious.com, which has no IPv6 address.
Set it up
Copy your API key and secret
Open Account › API access and copy the API Key and the API Secret. The full list needs a Basic, Pro, or Enterprise plan; with a Free key the relay receives the 10% sample, and refuses it.Store the credentials on the relay host
Create/etc/ismalicious/netrcroot-only with the commands below, then write the three lines into it with an editor: typed in a shell, they would land in its history. curl reads the key and the secret from the file, so they never appear on a command line, and pfSense never holds them.Create the file, root-onlysh install -d -m 700 /etc/ismalicious [ -e /etc/ismalicious/netrc ] || install -m 600 /dev/null /etc/ismalicious/netrc chmod 600 /etc/ismalicious/netrc # Then write the three lines below into it with an editor, unless it holds them already./ etc/ ismalicious/ netrcnetrc machine api.ismalicious.com login <API_KEY> password <API_SECRET>Install the fetch script and its timer
- Save the script as
/, mode 755, with the service and timer units underusr/ local/ sbin/ ismalicious-mirror /etc/systemd/system/, then run the enable block: it creates/var/www/ismalicious, starts the timer and runs the first fetch at once, since the alias step needs the files. - The service runs as root to read the netrc, and may write nothing but that directory.
- Each hour the script downloads every list in
LISTS, refuses an error status, a body that is not a list, the 10% sample and a file whose entry count differs from its header, and replaces each file in one move, so the firewall never reads half a file. - A refused list keeps its previous copy, and the run then exits 1, so
systemctl --failedshows it.
/ usr/ local/ sbin/ ismalicious-mirrorsh #!/bin/sh # Fetch the lists with your key and publish them for the firewall. # A list that fails a check keeps its previous copy, and the run exits 1. set -eu NETRC=/etc/ismalicious/netrc DEST=/var/www/ismalicious LISTS="blocklist-ips-critical.txt blocklist-domains-c2.txt" # fetch_list LIST OUT: download one list to OUT and check it. # On any failure OUT is removed and the function returns 1. fetch_list() { if ! code=$(curl --silent --show-error --fail --netrc-file "$NETRC" \ --proto '=https' --max-time 300 --retry 2 \ --output "$2" --write-out '%{http_code}' "https://api.ismalicious.com/blocklist/download/$1"); then rm -f "$2"; echo "$1: download failed" >&2; return 1 fi if [ "$code" != 200 ]; then rm -f "$2"; echo "$1: HTTP $code" >&2; return 1 fi if ! head -n 1 "$2" | grep -q '^[#!] IsMalicious.com Blocklist'; then rm -f "$2"; echo "$1: not an isMalicious list" >&2; return 1 fi if grep -q 'Lite Version' "$2"; then rm -f "$2"; echo "$1: lite list received, check the API key and the plan" >&2 return 1 fi # Every entry ends with a newline, and one header counts them: refuse a # cut, doubled or empty file. if [ -n "$(tail -c 1 "$2")" ]; then rm -f "$2"; echo "$1: cut short, no final newline" >&2; return 1 fi if [ "$(grep -c '^[#!] Total entries:' "$2")" != 1 ]; then rm -f "$2"; echo "$1: not one Total entries line" >&2; return 1 fi total=$(sed -n 's/^[#!] Total entries: \([0-9,]*\)$/\1/p' "$2" | tr -d ,) got=$(grep -c '^[^#!]' "$2" || true) if [ -z "$total" ] || [ "$total" = 0 ] || [ "$got" != "$total" ]; then rm -f "$2"; echo "$1: $got entries, the header says ${total:-none}" >&2 return 1 fi } mkdir -p "$DEST" failed=0 for f in $LISTS; do tmp=$(mktemp "$DEST/.$f.XXXXXX") if fetch_list "$f" "$tmp"; then chmod 644 "$tmp" mv -f "$tmp" "$DEST/$f" else failed=1 fi done exit "$failed"/ etc/ systemd/ system/ ismalicious-mirror. servicesystemd [Unit] Description=Fetch isMalicious blocklists Wants=network-online.target After=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/ismalicious-mirror # Root, to read the root-only netrc; it may write only the published directory. NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes PrivateTmp=yes ReadWritePaths=/var/www/ismalicious/ etc/ systemd/ system/ ismalicious-mirror. timersystemd [Unit] Description=Fetch isMalicious blocklists every hour [Timer] OnCalendar=hourly RandomizedDelaySec=15min Persistent=true [Install] WantedBy=timers.targetEnable the timer and run the first fetchsh install -d -m 755 /var/www/ismalicious systemctl daemon-reload systemctl enable --now ismalicious-mirror.timer systemctl start ismalicious-mirror.service # the first fetch, now ls -l /var/www/ismalicious/- Save the script as
Serve the lists over HTTPS on your network
- Publish
/var/www/ismaliciousashttps:/with a certificate pfSense trusts: a public one, or a private CA added under System › Certificates › Authorities with Add this Certificate Authority to the Operating System Trust Store ticked./ RELAY_HOST/ ismalicious/ - Refuse dot files in the web server, since downloads in progress sit there, and allow only the firewall’s address, as in the nginx example below.
- Keep the relay internal: each file is licensed to the account that downloads it, and its header forbids redistributing the compilation.
nginx, in the relay’s server blocknginx location /ismalicious/ { allow <FIREWALL_IP>; deny all; # Downloads in progress are dot files: never serve them. location ~ /\. { deny all; } }- Publish
Load the IPs as a URL Table alias
- In System › Advanced › Firewall & NAT, tick Check certificate of aliases URLs, which applies to every URL alias, and set Firewall Maximum Table Entries to 600,000 or more: twice the total of all tables, the bogon tables included.
- Then Firewall › Aliases › URLs › Add: name
isMalicious_IPs, type URL Table (IPs), URLhttps:/, 1 day in the drop-down after the slash; Save and Apply Changes./ RELAY_HOST/ ismalicious/ blocklist-ips-critical. txt - On Firewall › Rules › WAN, add a rule with Action Block, Address Family IPv4+IPv6, Protocol Any and Source
isMalicious_IPs; on LAN the same with DestinationisMalicious_IPs, added with Add rule to the top of the list so it sits above the default allow rule. - Tick Log packets that are handled by this rule on both, then Apply Changes.
Or load the lists with pfBlockerNG
- Enable pfBlockerNG in Firewall › pfBlockerNG › General.
- In IP, under IP Interface/Rules Configuration, select WAN in Inbound Firewall Rules and LAN in Outbound Firewall Rules, then Save: without them, Deny Both creates no rule.
- In IP › IPv4 › Add, add a source with Format Auto, State ON, the relay URL of
blocklist-ips-critical.txtand the labelisMalicious; set the Action to Deny Both and the Update Frequency to every hour or every 12 hours, then repeat in the IPv6 tab with the same source. - For domains, tick Enable DNSBL under DNSBL, then DNSBL › DNSBL Groups › Add: Name
isMalicious_C2, a source with Format Auto, State ON and the relay URL ofblocklist-domains-c2.txt, Action Unbound (the default, Disabled, blocks nothing), Update Frequency every hour; Save. - Run Update after each change.
Verify it works
- On the relay, the entry count of each published file matches the list’s
countin /blocklist/stats, give or take one rebuild. - Diagnostics › Tables, with
isMalicious_IPsselected, shows the entry count and the addresses. - Status › System Logs › System › General shows
Updatedafter each daily refresh (12:30 by default); Update in Diagnostics › Tables forces one now.isMalicious_IPs content from - With pfBlockerNG, the Update output and the Logs tab report each download, the IP tables appear in Diagnostics › Tables, and DNSBL hits under Reports.
- The firewall logs show the sessions the Block rules stop.
# The relay: next run, last runs, and the entries it published.
systemctl list-timers ismalicious-mirror.timer
journalctl -u ismalicious-mirror.service
grep -c -v '^#' /var/www/ismalicious/blocklist-ips-critical.txtTroubleshooting
The relay prints “download failed”
A 401 means the key or the secret in the netrc file is wrong or incomplete: curl exits with code 22 and the relay keeps the previous file, which pfSense keeps serving. Copy both values again from Account › API access: regenerating the key there invalidates the old pair.
Only about 10% of the entries load
pfSense or pfBlockerNG points straight at api.ismalicious.com: neither has a credential option for a list (a key put in the URL is written to the system log). Or the plan is Free or lapsed: past due, unpaid, canceled, incomplete or paused counts as Free. The relay refuses the sample; a direct feed shows Lite Version in its header.
The relay prints “entries, the header says”, “cut short, no final newline” or “not one Total entries line”
The file was empty, cut short or not one of the lists, as a proxy or captive portal answers. The relay keeps the previous copy and the next run tries again.
The ruleset fails to load
The pf tables are too small: Netgate warns that the firewall may then fail to load the ruleset. Raise Firewall Maximum Table Entries to twice the total of all tables, the bogon tables (about 160,000 entries) included, or load a smaller list.
Timeouts on a large list
A download that takes longer than 30 seconds on our side ends in a timeout or a 502: use a tier or category list from the table.
DNSBL logs every entry as invalid
The # header lines are comments to both parsers, so they are not the cause: an -adguard or -dnsmasq file is. Point DNSBL at the plain domain file.
“Unable to fetch usable data from URL” on save
pfSense downloads the URL when you save the alias. Check the relay URL, its certificate, and that the file exists.
Certificate errors
With Check certificate of aliases URLs ticked, the relay’s certificate must chain to a CA in pfSense’s trust store. In pfBlockerNG, fix the certificate rather than switching the source to FLEX, which stops verifying it after an error.
403 from ismalicious.com
The ismalicious.com edge refuses some sources. Point the relay at api.ismalicious.com.
Limits
- Neither pfSense nor pfBlockerNG has a credential option for a list (a key put in the URL is written to the system log), so the full list needs the relay host.
- A URL Table alias refreshes in days, checked once a day. To refresh every hour, use pfBlockerNG.
- An IP alias resolves every line that looks like a host name through DNS: never point it at a domain list.
- No CIDR is served: URL Table aliases take addresses and CIDR, not ranges, and the IP lists hold single addresses.
- DNSBL memory use grows with the list, and pfBlockerNG documents no limit: start with the category lists rather than the 2 million critical domains.
- The 10% sample is the first tenth of an unsorted file, not the riskiest tenth.
Questions
Can pfSense or pfBlockerNG send an API key?
No. Neither offers a credential field for a list, so pointed straight at api.ismalicious.com they receive the 10% sample. A host you control fetches the full list with your key and serves it to the firewall over HTTPS.
Should I use a URL Table alias or pfBlockerNG?
A URL Table alias is built in and refreshes once a day at most. pfBlockerNG refreshes as often as every hour, takes IPv6 lists in its own tab, and blocks domains through DNSBL.
How often should pfBlockerNG update the lists?
Every hour or every 12 hours. The lists are rebuilt every 12 hours, and the relay fetches them hourly.
Does the critical IP list fit in pfSense’s tables?
Not with the default on a standard install. The 400,000 default is sized for the IPv6 bogon table (about 158,000 entries), and Netgate requires room for twice the total of all tables: with the critical IPs, about 500,000. Set Firewall Maximum Table Entries to 600,000 or more; Netgate budgets about 1 KB of RAM per entry.
Can pfSense block the domain lists?
With pfBlockerNG’s DNSBL, using the plain domain files from the relay. Do not load a domain list into a URL Table alias: pfSense would resolve every name through DNS.
Related
IP and domain feeds with basic authentication
External Dynamic Lists sized to PAN-OS limits
URL Table aliases with Basic authorization
Threat domains as a RouterOS DNS adlist
Threat IPs as nftables sets on any Linux host
Keep firewall blocklists current
Every list, level and category
TAXII 2.1 collections for SIEMs and TIPs
Authentication, endpoints and limits
Check one address before you block it
Get Started
Ready to get started?
No credit card required · Free API key