I attempted to download the zone using dig from my workplace and encountered the same limitation — the ad-block zone returns the same number of lines as it does from my home network. I’m not sure if this is directly relevant, but I thought it was worth mentioning.
After further investigation, the only workaround I’ve found is to create a script that downloads the enabled zones, places them into the cache folder, and restarts Unbound. I’ve included the script below in case anyone else runs into this problem.
That said, I believe the root cause should be addressed — either through a more reliable initial download of the blocklist, or at the very least, a more visible notification in the web UI when a list fails to load.
#!/bin/bash
#
# IPFire DBL to Unbound RPZ Zone Converter
#
# Downloads IPFire domain blocklists and converts them to RPZ zone files.
# Designed to be run via cron for automatic updates.
#
# Usage: ./download-rpz.sh
#
# Reads /etc/unbound/dnsbl.conf to find enabled RPZ zones.
# If unable to parse, falls back to downloading all lists.
#
# ---- CONFIGURATION ----
# Unbound DNSBL config file to parse for enabled lists
DNSBL_CONF="/etc/unbound/dnsbl.conf"
# All available IPFire lists (used as fallback)
ALL_LISTS="ads dating doh gambling games malware phishing piracy porn shopping smart-tv social streaming violence"
# Where to store zone files
ZONE_DIR="/var/cache/unbound"
# Base URL
BASE_URL="https://dbl.ipfire.org/lists"
# RPZ action: CNAME . = NXDOMAIN, CNAME *.= NODATA
RPZ_ACTION="CNAME ."
# ---- END CONFIGURATION ----
# Parse enabled lists from dnsbl.conf
if [ -f "$DNSBL_CONF" ]; then
LISTS=$(grep -oP '(?<=name:\s)[\w-]+(?=\.rpz\.ipfire\.org)' "$DNSBL_CONF" | sort -u | tr '\n' ' ')
if [ -z "$LISTS" ]; then
# Fallback: try alternative grep without -P
LISTS=$(grep 'name:' "$DNSBL_CONF" | grep 'rpz.ipfire.org' | sed 's/.*name:[[:space:]]*//' | sed 's/\.rpz\.ipfire\.org.*//' | sort -u | tr '\n' ' ')
fi
fi
if [ -z "$LISTS" ]; then
echo "WARNING: Could not parse $DNSBL_CONF or file not found."
echo "Falling back to downloading ALL lists."
LISTS="$ALL_LISTS"
else
echo "Found enabled lists in $DNSBL_CONF:"
echo " $LISTS"
fi
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%S+00:00)
SERIAL=$(date +%s)
for LIST in $LISTS; do
echo "Processing: $LIST"
DOMAIN_URL="${BASE_URL}/${LIST}/domains.txt"
ZONE_FILE="${ZONE_DIR}/${LIST}.rpz.ipfire.org.zone"
ZONE_NAME="${LIST}.rpz.ipfire.org"
TMP_FILE=$(mktemp)
# Download the domain list
HTTP_CODE=$(curl -s -w '%{http_code}' -o "$TMP_FILE" "$DOMAIN_URL")
if [ "$HTTP_CODE" != "200" ]; then
echo " ERROR: Failed to download $LIST (HTTP $HTTP_CODE)"
rm -f "$TMP_FILE"
continue
fi
# Count domains
TOTAL=$(grep -cv '^#\|^$' "$TMP_FILE")
echo " Downloaded $TOTAL domains"
# Extract description from the file header
DESCRIPTION=$(grep '^# Blocks\|^# Contains' "$TMP_FILE" | head -1 | sed 's/^# *//')
# Build zone file
{
# SOA record
echo "${ZONE_NAME}. 60 IN SOA primary.dbl.ipfire.org. hostmaster.ipfire.org. ${SERIAL} 3600 600 3600000 60"
# NS record
echo "${ZONE_NAME}. 60 IN NS primary.dbl.ipfire.org."
# TXT info records
echo "_info.${ZONE_NAME}. 60 IN TXT \"license=CC BY-SA 4.0\""
echo "_info.${ZONE_NAME}. 60 IN TXT \"total-domains=${TOTAL}\""
echo "_info.${ZONE_NAME}. 60 IN TXT \"updated-at=${TIMESTAMP}\""
[ -n "$DESCRIPTION" ] && echo "_info.${ZONE_NAME}. 60 IN TXT \"description=${DESCRIPTION}\""
# Convert domains to RPZ entries (append zone name to match AXFR format)
grep -v '^#\|^$' "$TMP_FILE" | while IFS= read -r domain; do
echo "${domain}.${ZONE_NAME}. 60 IN ${RPZ_ACTION}"
echo "*.${domain}.${ZONE_NAME}. 60 IN ${RPZ_ACTION}"
done
} > "${ZONE_FILE}.tmp"
# Atomic replace and set permissions
mv "${ZONE_FILE}.tmp" "$ZONE_FILE"
chmod 644 "$ZONE_FILE"
chown nobody:nobody "$ZONE_FILE"
echo " Written to $ZONE_FILE"
rm -f "$TMP_FILE"
done
# Reload Unbound
echo "Reloading Unbound..."
if unbound-control reload 2>/dev/null; then
echo "Done - all zones loaded."
else
echo "WARNING: unbound-control reload failed. You may need to restart unbound manually."
fi