Skip to main content

Setup guide

Pi-hole malware blocklist as an authenticated adlist

Pi-hole hands the credentials in an adlist address to curl as HTTP Basic, so gravity loads the full isMalicious list. Run gravity every 12 hours to follow the rebuilds.

No credit card required · Free API key

Data path
  1. isMalicious

    api.ismalicious.com

    blocklist-domains-critical.txt

    Rebuilt every 12 h

    1. Relay

      Step 6

      Keep the key out of Pi-hole (optional)

  2. Pi-hole

    Step 3

    Add the list to Pi-hole

  3. Run gravity

    Step 4
On this page08

What you get

Pi-hole blocks names, not addresses, so only the domain lists apply. Load one critical list, in plain or adblock syntax, and add the category lists if you want them.

ListEntriesRebuiltPlans
blocklist-domains-critical.txtDefault. Domains listed by 6 or more threat sources, or 3 or more with a critical category. Exact-name blocking.about 2 millionevery 12 hBasic, Pro, and Enterprise
blocklist-domains-critical-adguard.txtThe same domains as ||domain^ rules, which also block subdomains. Core v5.16.1 and FTL v5.22 or later. Load this one or the plain list, not both.about 2 millionevery 12 hBasic, Pro, and Enterprise
blocklist-domains-c2.txtCommand-and-control domains reported by C2 trackers, whatever their level.about 22,000every 12 hBasic, Pro, and Enterprise
blocklist-domains-ransomware.txtDomains in the ransomware category, whatever their level.about 3,600every 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-domains-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 - Domains (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

  • Pi-hole Core v5.2.3 or later: earlier releases reject an @ in a list address. Pi-hole v6 is the current line.
  • A shell on the Pi-hole host, or on the Docker host, to schedule gravity.
  • An isMalicious API key and secret, from Account › API access.
  • Outbound HTTPS (TCP 443) over IPv4 from Pi-hole 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 loads the 10% sample.
  2. Build the list address

    Put the key, a colon and the secret before the host, followed by @. Keys and secrets are UUIDs, so they need no encoding. Use api.ismalicious.com: the ismalicious.com edge refuses some sources with a 403.
    List addressURL
    https://<API_KEY>:<API_SECRET>@api.ismalicious.com/blocklist/download/blocklist-domains-critical.txt
    List address, subdomains includedURL
    https://<API_KEY>:<API_SECRET>@api.ismalicious.com/blocklist/download/blocklist-domains-critical-adguard.txt
  3. Add the list to Pi-hole

    Pi-hole v6: Group Management › Lists, paste the address in Address, enter a Comment such as isMalicious critical, keep the Default group and click Add blocklist. Pi-hole v5: Group Management › Adlists, then Address, Comment and Add. On v6 the API does the same: the block below verifies FTL’s own certificate, reads the password from the terminal and the address from a file, so neither lands in a process listing or the shell history, and closes the session at the end.
    Pi-hole v6 APIsh
    # FTL's own CA, copied from /etc/pihole/tls_ca.crt on the Pi-hole host. Add
    # --resolve pi.hole:443:<PIHOLE_IP> when pi.hole does not resolve.
    CA=./pihole-tls_ca.crt
    API=https://pi.hole/api
    
    # The password is read from the terminal: it stays out of ps and history.
    # jq escapes the quotes and backslashes it may hold.
    printf 'Pi-hole password: ' >&2; stty -echo; IFS= read -r PW; stty echo; echo >&2
    SID=$(printf '%s' "$PW" | jq -Rs '{password: .}' |
      curl -sS --cacert "$CA" -X POST "$API/auth" --data @- | jq -r .session.sid)
    
    # list.json (mode 600), written with an editor, holds the address with the key:
    # {"address":"https://<API_KEY>:<API_SECRET>@api.ismalicious.com/blocklist/download/blocklist-domains-critical.txt","comment":"isMalicious critical","groups":[0]}
    curl -sS --cacert "$CA" -X POST "$API/lists?type=block" -H "X-FTL-SID: $SID" \
      -H "Content-Type: application/json" --data @list.json
    
    curl -sS --cacert "$CA" -X POST "$API/action/gravity" -H "X-FTL-SID: $SID"
    curl -sS --cacert "$CA" -X DELETE "$API/auth" -H "X-FTL-SID: $SID"
  4. Run gravity

    Tools › Update Gravity › Update, or sudo pihole -g on the host. Gravity downloads the list and writes it to gravity.db.
  5. Run gravity every 12 hours

    Pi-hole updates its lists once a week, at a random minute in the night. Create a cron file of your own with the updateGravity line of /etc/cron.d/pihole and a 12-hour schedule outside hours 3 and 4, where the weekly run falls: gravity has no lock. Do not edit /etc/cron.d/pihole: updates overwrite it. The line runs as pihole on v6 and as root on v5. On Docker, schedule pihole -g through docker exec on the host.
    /etc/cron.d/ismalicious-gravitycron
    # Pi-hole v6 (user pihole; on v5, write root). The lists are rebuilt every 12 hours.
    17 5,17 * * *   pihole    PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updateGravity >/var/log/pihole/pihole_updateGravity.log || cat /var/log/pihole/pihole_updateGravity.log
  6. Keep the key out of Pi-hole (optional)

    • Pi-hole stores the address in clear in gravity.db, shows it on the Lists page and prints it in every gravity log.
    • To avoid that, create the root-only netrc file below, fetch the list with the script, which refuses an error, a body that is not a list, the 10% sample and a file whose entry count differs from its header, and add file:///var/lib/ismalicious/blocklist-domains-critical.txt as the address.
    • The script makes the copy readable for gravity.
    • On Docker, mount /var/lib/ismalicious into the container read-only and keep the same file:// address.
    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-fetch.shsh
    #!/bin/sh
    # Runs as root, before gravity. /etc/ismalicious/netrc (mode 600) holds the key.
    # The copy is made readable for gravity, which reads it as pihole (Core
    # v6.4.2+) or as root; on Docker there is no pihole user on the host.
    set -eu
    NETRC=/etc/ismalicious/netrc
    DIR=/var/lib/ismalicious
    FILE=blocklist-domains-critical.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 "$DIR"
    tmp=$(mktemp "$DIR/.$FILE.XXXXXX")
    trap 'rm -f "$tmp"' EXIT
    fetch_list "$FILE" "$tmp"
    chmod 644 "$tmp"
    mv -f "$tmp" "$DIR/$FILE"
    
    # Schedule it before gravity, in /etc/cron.d/ismalicious-gravity:
    # 7 5,17 * * *   root   /usr/local/sbin/ismalicious-fetch.sh
    # List address in Pi-hole: file:///var/lib/ismalicious/blocklist-domains-critical.txt

Verify it works

  • Gravity prints Status: Retrieval successful for the list, then Parsed N exact domains and M ABP-style domains (from Core v5.17; older releases print Imported N domains).
  • N for a plain list, or M for the adblock one, matches the list’s count in /blocklist/stats, give or take one rebuild. About a tenth of it is the 10% sample.
  • Group Management › Lists shows the list as downloaded or unchanged, not as failed.
  • On Core v6.1 or later, the first lines of the cached copy in /etc/pihole/listsCache/ (mode 0640: read it with sudo) show # Total entries: with no Lite Version mention. A file:// list is cached as list.N.local.domains.
  • pihole -q on a domain from the list names the lists that contain it.
Checkssh
# Gravity's summary for the list (Core v5.17+ prints Parsed, older Imported):
sudo pihole -g | grep -E 'Retrieval|Parsed|Imported'

# Core v6.1+ keeps the raw file (mode 0640): a full list has no "Lite Version".
sudo head -12 /etc/pihole/listsCache/list.*.api.ismalicious.com.domains

# The same check without Pi-hole, with the netrc file of the local copy:
sudo curl -sS --netrc-file /etc/ismalicious/netrc -D - -o /dev/null \
  "https://api.ismalicious.com/blocklist/download/blocklist-domains-critical.txt" | grep -i '^x-blocklist-version'

Troubleshooting

Gravity reports a 401

The key or the secret is wrong or incomplete. Core v6.4.2 and later print The requested URL returned error: 401; older releases print the URL, credentials included. Gravity then keeps its previous copy, so the old list stays active: check the list status after every key rotation. The response body reads “Blocklist not found or empty”; trust the status code. Copy both values again from Account › API access.

Only about 10% of the domains load

The address carries no credentials, or the plan is Free or lapsed: past due, unpaid, canceled, incomplete or paused counts as Free. Check the header of the cached copy and your plan.

The local copy script 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 script keeps the previous copy and exits 1, and gravity keeps reading it.

Gravity says “Invalid Target”

The address holds a character Pi-hole refuses besides the one @, often a placeholder bracket left in or a space. Before Core v5.2.3, any @ is refused: upgrade.

502, 504 or a timeout

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.

503 Service Unavailable

The list is being built for the first time, and the response carries Retry-After: 600. Gravity does not retry: it keeps its cached copy until the next run.

404 Not Found

The filename is wrong. Copy it from the table above.

Gravity ignores most of the entries

The header lines are not the cause: Pi-hole skips # and ! lines. When the ignored count is close to the entry count, the wrong file is loaded: a -dnsmasq.txt list, a URL list, or an -adguard.txt list on Pi-hole older than Core v5.16.1 and FTL v5.22. An IP list is skipped silently from Core v6.1 (0 parsed, 0 ignored), and imported before as names nothing matches.

Certificate errors

Gravity’s curl checks the system CA store. Core v6.4.2 and later print curl’s message; older releases report “Connection Refused”. Update ca-certificates, check the system clock, and exempt api.ismalicious.com from TLS inspection.

Limits

  • Pi-hole keeps the address, credentials included, in gravity.db (left mode 664) and up to 10 backups of it under /etc/pihole/gravity_backups/, on the Lists page, in pihole -q output and in every gravity log. The file:// relay of the last step avoids that: rotate the key after switching to it.
  • A plain list blocks the listed names only. The adblock variant also blocks every subdomain, which reaches more names than the list holds.
  • Pi-hole cannot block IP addresses: from Core v6.1 it skips IP lines silently, and before it imported them as names nothing matches, so an IP list blocks nothing. Load blocklist-ips-* lists on a firewall.
  • No RPZ, no CIDR and no wildcard entries such as *.example are served; Pi-hole needs none of them.
  • The 10% sample is the first tenth of an unsorted file, not the riskiest tenth.
  • Without your own cron file, Pi-hole runs gravity once a week and can be up to 7 days behind the lists.

Questions

Can Pi-hole send an API key for a blocklist?

Yes. Put the key and the secret before the host in the list address, separated by a colon and followed by @. Pi-hole hands the address to curl, which sends them as HTTP Basic. It needs Core v5.2.3 or later. Pi-hole stores and prints that address in clear; fetching the list with a script and loading it as a file:// address keeps the key on the host.

Does the list block subdomains?

The plain list blocks exact names only. Load blocklist-domains-critical-adguard.txt instead: Pi-hole reads its ||domain^ rules as the domain and all its subdomains, from Core v5.16.1 and FTL v5.22.

Can Pi-hole block malicious IP addresses?

No. Pi-hole answers DNS queries, so it blocks names only: from Core v6.1 it skips IP lines silently. Load the IP lists on a firewall instead.

How often should gravity run?

Every 12 hours. The lists are rebuilt every 12 hours, and Pi-hole’s own schedule is weekly. Running gravity more often downloads the same file again.

What does a free account load into Pi-hole?

The first 10% of each list. Gravity loads it without an error, so compare the entry count with the published count. The full list needs a Basic, Pro, or Enterprise plan.

Get Started

Ready to get started?

No credit card required · Free API key