Skip to main content

Setup guide

OPNsense blocklist of malicious IPs, as an authorized URL Table alias

OPNsense 25.1 and later send Basic authorization with a URL Table alias, so the firewall fetches the full isMalicious IP list itself. Its Unbound DNS blocklists cannot authenticate: the domain lists go through a relay you control.

No credit card required · Free API key

Data path
  1. isMalicious

    api.ismalicious.com

    blocklist-ips-critical.txt

    Rebuilt every 12 h

    1. Relay

      Step 5

      Set up a relay for the domain lists (optional)

  2. OPNsense

    Step 3

    Add the URL Table alias

  3. Block the alias in firewall rules

    Step 4
On this page08

What you get

IP lists go to a URL Table (IPs) alias, which OPNsense fetches with your key. Domain lists go to Unbound’s DNS blocklists, which take no credentials, so they come from a relay.

ListEntriesRebuiltPlans
blocklist-ips-critical.txtDefault alias. IPs listed by 6 or more threat sources, or 3 or more with a critical category.about 89,000every 12 hBasic, Pro, and Enterprise
blocklist-ips-c2.txtCommand-and-control IPs reported by C2 trackers: a smaller alias.about 44,000every 12 hBasic, Pro, and Enterprise
blocklist-domains-c2.txtUnbound blocklist, through the relay: command-and-control domains reported by C2 trackers.about 22,000every 12 hBasic, Pro, and Enterprise
blocklist-domains-ransomware.txtUnbound blocklist, through the relay: domains in the ransomware category, whatever their level.about 3,600every 12 hBasic, Pro, and Enterprise
blocklist-domains-cryptomining.txtUnbound blocklist, through the relay: domains in the cryptomining category, whatever their level.about 6,100every 12 hBasic, 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.

Compare plans

What a download returnsHTTP

GET https://api.ismalicious.com/blocklist/download/blocklist-ips-critical.txt

Full list or 10% sample

FieldBasic, Pro, and EnterpriseFree
X-Blocklist-Version:fulllite
X-Blocklist-Percentage:10010
Total entries:<COUNT><COUNT> (Lite Version - 10% of <TOTAL>)

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

  • OPNsense 25.1 or later, the first release with authorization on URL aliases; menus checked against 25.1 to 26.7.
  • For the domain lists only: a Linux host with systemd, curl and an HTTPS web server, which OPNsense reaches on your network.
  • An isMalicious API key and secret, from Account › API access.
  • Outbound HTTPS (TCP 443) over IPv4 from OPNsense, and from the relay host for the DNS blocklist, to api.ismalicious.com, which has no IPv6 address.

Set it up

  1. 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; a Free key, or a lapsed plan, loads the 10% sample.
  2. Turn on certificate checking for alias downloads

    In Firewall › Settings › Advanced (on the development branch, Firewall › Advanced), tick Check certificate of aliases URLs, before you save the alias. The setting is global, off in the factory configuration, and without it OPNsense sends the API secret to any server that answers. Save.
  3. Add the URL Table alias

    Firewall › Aliases, then +: enable it, name it isMalicious_IPs, choose the type URL Table (IPs), set Refresh Frequency to 0 days and 1 hour (12 hours is enough: the lists are rebuilt every 12 hours), paste the list URL in Content, and set Authorization to Basic with the API key as Username and the API secret as Password. Save, then Apply.
    Alias fieldsGUI
    Type
    URL Table (IPs)
    Refresh Frequency
    0 days, 1 hour
    Content
    https://api.ismalicious.com/blocklist/download/blocklist-ips-critical.txt
    Authorization
    Basic
    Username
    <API_KEY>
    Password
    <API_SECRET>
    Type:               URL Table (IPs)
    Refresh Frequency:  0 days, 1 hour
    Content:            https://api.ismalicious.com/blocklist/download/blocklist-ips-critical.txt
    Authorization:      Basic
    Username:           <API_KEY>
    Password:           <API_SECRET>
  4. Block the alias in firewall rules

    • 25.1 to 26.1: on Firewall › Rules › WAN, add a Block rule with isMalicious_IPs as the source; on Firewall › Rules › LAN, a Block rule with it as the destination, above the allow rule; Apply.
    • 26.7: Firewall › Rules, select WAN, +, Action Block, Version any, Source isMalicious_IPs, Save; select LAN, +, Action Block, Version any, Destination isMalicious_IPs, Save, then Move selected rule before this rule on the default allow rule; Apply.
    • The TCP/IP version defaults to IPv4 on every release: set it to IPv4+IPv6, or any.
  5. Set up a relay for the domain lists (optional)

    • Unbound’s blocklists download without credentials, so a host you control fetches the domain lists with your key and serves them over HTTPS.
    • Create /etc/ismalicious/netrc root-only with the commands below and write the three lines into it with an editor; install the script as /usr/local/sbin/ismalicious-mirror, mode 755, with its service and timer units, and run the enable block, which creates /var/www/ismalicious and runs the first fetch at once.
    • The service runs as root to read the netrc, and may write nothing but that directory.
    • The script refuses an error, a body that is not a list, the 10% sample and a file whose entry count differs from its header.
    • Publish /var/www/ismalicious as https://RELAY_HOST/ismalicious/ with a certificate OPNsense trusts (a private CA goes under System › Trust › Authorities), refuse dot files in the web server, as in the nginx example, and keep it internal: each file is licensed to the account that downloads it, and its header forbids redistributing the compilation.
    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>
    /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-domains-c2.txt blocklist-domains-ransomware.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.target
    Enable 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/
    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; }
    }
  6. Add the relay URL to Unbound’s blocklists

    • 25.7.8 and later: Services › Unbound DNS › Blocklists, Blocklists tab, +.
    • Switch on advanced mode, tick Enable, add https://RELAY_HOST/ismalicious/blocklist-domains-c2.txt and https://RELAY_HOST/ismalicious/blocklist-domains-ransomware.txt to URLs of Blocklists, set Cache TTL to 43200 (12 hours), tick Return NXDOMAIN or enter a Destination Address, type a Description, Save, then Apply.
    • 25.1 to 25.7.7: Services › Unbound DNS › Blocklist, advanced mode, Enable, the same URLs, Return NXDOMAIN or a Destination Address, Apply; these releases have no Cache TTL field and download a list again after 20 hours.
  7. Schedule the blocklist update

    Unbound’s blocklists refresh only from a cron task: System › Settings › Cron, then +, with the command Update Unbound DNSBLs every hour (minute 15, hour *) and a Description, which is required. Save, then Apply. A list is downloaded again once its cached copy is older than the Cache TTL.

Verify it works

  • Firewall › Diagnostics › Aliases, with isMalicious_IPs selected, lists the current entries: about the list’s count in /blocklist/stats.
  • The OPNsense API returns the same entries, with an OPNsense API key: from 25.7 a request returns at most 9,999 rows, so read total.
  • Firewall › Log Files › General shows fetch alias url with the line count after each download, or error fetching alias url with the HTTP code.
  • Services › Unbound DNS › Log File shows blocklist download with the lines downloaded for each URL; from 25.7.8 the Blocklist Tester checks a domain against your settings.
OPNsense APIsh
# The alias entries, through the OPNsense API: an OPNsense API key with the
# "Diagnostics: PF Table IP addresses" privilege. curl asks for its secret;
# the GUI CA (System › Trust › Authorities) verifies the firewall.
curl -s --cacert opnsense-gui-ca.pem -u '<OPN_KEY>' \
  "https://<FIREWALL>/api/firewall/alias_util/list/isMalicious_IPs" | grep -o '"total":[0-9]*'

Troubleshooting

error fetching alias url … [http_code:401]

The key or the secret is wrong or incomplete, and the alias keeps its previous content. The response body reads “Blocklist not found or empty”; trust the status code. Enter both values again from Account › API access: regenerating the key there invalidates the old pair.

Only about 10% of the entries load

Authorization is not set, OPNsense is older than 25.1, or the plan is Free or lapsed (past due, unpaid, canceled, incomplete or paused counts as Free). The file’s Total entries line then says Lite Version. For an Unbound blocklist pointed straight at api.ismalicious.com, this is expected: use the relay.

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.

“Error loading alias [isMalicious_IPs]”

Firewall › Log Files › General shows the table could not take the entries. Raise Firewall Maximum Table Entries above the total of your aliases, or use 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.

The Unbound blocklist loads nothing

Both parsers ignore the # header lines, so they are not the cause: an -adguard or -dnsmasq file is. Point Unbound at the plain domain file.

Certificate errors after ticking the check

api.ismalicious.com uses a Let’s Encrypt certificate: the firewall’s CA store must trust ISRG Root X1 or X2. For the relay, its certificate must chain to a CA OPNsense trusts.

403 from ismalicious.com

The ismalicious.com edge refuses some sources. Use api.ismalicious.com.

Limits

  • Certificate checking for alias downloads is off by default: with it off, the Basic credentials go to whoever answers. Tick it before saving the alias.
  • OPNsense stores the key and the secret in clear in config.xml, its backups, /usr/local/etc/filter_tables.conf and alias exports: use a key dedicated to this firewall, and encrypt the backups.
  • Unbound’s DNS blocklists have no credential field: the full domain lists need the relay.
  • An IP alias resolves every line that is not an address through DNS: never point it at a domain list.
  • No CIDR or ranges are served: the alias would accept them, but the IP lists hold single addresses.
  • The 10% sample is the first tenth of an unsorted file, not the riskiest tenth.

Questions

Can OPNsense send an API key for a URL Table alias?

Yes, from OPNsense 25.1. Set the alias’s Authorization to Basic, with the API key as username and the API secret as password: OPNsense sends them with the first request, so the alias loads the full list.

Why tick Check certificate of aliases URLs?

It is off in the factory configuration, and without it OPNsense does not verify the server it sends the credentials to. Tick it before saving an alias that carries the API secret.

Can the Unbound DNS blocklist use the API key?

No. Unbound’s blocklists download without credentials and get the 10% sample. Serve them the full domain lists from a relay that fetches them with your key.

Which refresh frequency should the alias use?

1 hour, or 12 hours. The lists are rebuilt every 12 hours; OPNsense checks every minute and fetches an alias again once its frequency has elapsed.

Will the list fit in OPNsense’s tables?

The default table size is at least 1 million entries on 25.1, and grows with the RAM from 25.7. OPNsense replaces an alias’s table in place, so the total of your aliases must stay under Firewall Maximum Table Entries.

Get Started

Ready to get started?

No credit card required · Free API key