There is no useful warning before an IP is blacklisted. Spamhaus does not email you. Your ESP does not page on-call. The first sign is usually a drop in placement and a spike in support tickets two days later.
The only way to catch a listing early is to actively query the blacklists yourself, on a schedule, from a script you control. Here is how.
In 2026, four lists affect a non-trivial share of inbound mail at Gmail, Outlook, and large enterprise MTAs: Spamhaus ZEN (combines SBL, XBL, PBL, CSS), Barracuda, SpamCop, and SORBS. UCEPROTECT has fans and detractors; monitor level 1 but expect level 2/3 to fire a lot even on clean IPs.
How DNSBL queries work
A DNSBL (DNS-based block list) answers queries of the form reversed.ip.blacklist.example. If the query returns anything (usually a 127.0.0.x address), the IP is listed. NXDOMAIN means clean.
# Example: is 192.0.2.42 on Spamhaus ZEN?
# Reverse the IP octets: 42.2.0.192, append zen.spamhaus.org
dig +short 42.2.0.192.zen.spamhaus.org
# Returns 127.0.0.4 -> listed (in CSS)
# Returns nothing -> cleanIPv6 works the same way but with 32 nibbles reversed. Most blacklists support IPv6 badly; for IPv6 sending, rely on ESP-level reputation signals instead of DNSBL checks.
The check script
Reverses the IP once, fans out queries to ~10 blacklists in parallel, collects the verdicts, alerts if any fire. Runs in under a second.
#!/usr/bin/env bash
# /usr/local/bin/dnsbl_check.sh
set -euo pipefail
IP="$1"
SLACK="${SLACK_WEBHOOK}"
# Reverse the IP
IFS=. read -r a b c d <<< "$IP"
REV="$d.$c.$b.$a"
# Blacklists worth checking in 2026
BLACKLISTS=(
zen.spamhaus.org
b.barracudacentral.org
bl.spamcop.net
dnsbl.sorbs.net
dnsbl-1.uceprotect.net
psbl.surriel.com
bl.mailspike.net
dnsbl.invaluement.com
truncate.gbudb.net
bl.score.senderscore.com
)
LISTED=()
# Query each blacklist in parallel (subshell + wait)
for bl in "${BLACKLISTS[@]}"; do
(
RESULT=$(dig +time=2 +tries=1 +short "${REV}.${bl}." || true)
if [[ -n "$RESULT" ]]; then
echo "$bl -> $RESULT"
fi
) &
done
wait
# Rerun sequentially to collect results (simple approach)
for bl in "${BLACKLISTS[@]}"; do
RESULT=$(dig +time=2 +tries=1 +short "${REV}.${bl}." || true)
[[ -n "$RESULT" ]] && LISTED+=("$bl -> $RESULT")
done
if [[ ${#LISTED[@]} -gt 0 ]]; then
MSG="IP $IP listed on: ${LISTED[*]}"
curl -s -X POST "$SLACK" -H "Content-Type: application/json" \
-d "{\"text\":\":rotating_light: $MSG\"}"
echo "$MSG"
exit 2
fi
echo "OK - $IP clean on ${#BLACKLISTS[@]} blacklists"
exit 0Scheduling and rate limits
Most blacklists have a query rate policy. Spamhaus, in particular, will stop returning results if your resolver exceeds their query volume. For personal or small-business use the limits are generous; for a tool that checks on behalf of customers you need a Spamhaus Data Query Service (DQS) subscription and a dedicated resolver endpoint.
For most self-monitoring use cases:
- Every 15 minutes per IP is a safe rate for Spamhaus and the others. Do not go sub-minute.
- Use your own resolver, not 8.8.8.8. Spamhaus rate-limits Google's resolver heavily; queries may start returning empty even on genuinely listed IPs.
- Cache aggressively. A 5-minute cache per (IP, list) pair cuts query volume by 3x if you have multiple monitoring jobs.
If your checks always come back "clean" regardless of reality, you are probably rate-limited at the resolver level without knowing it. Test deliberately: query a known-listed test IP like 127.0.0.2 against zen.spamhaus.org. That address is guaranteed listed. If you get no response, your resolver is the problem.
Wiring to Nagios / Prometheus
The script above is already Nagios-compatible (exit codes 0/2, one-line status). For Prometheus, emit the count of listings as a gauge:
# Write to textfile collector
{
echo "# TYPE dnsbl_listings gauge"
for IP in 192.0.2.42 192.0.2.43; do
COUNT=$(./dnsbl_check.sh "$IP" 2>/dev/null | grep -c '->' || true)
echo "dnsbl_listings{ip=\"$IP\"} $COUNT"
done
} > /var/lib/node_exporter/textfile/dnsbl.prom.$$
mv /var/lib/node_exporter/textfile/dnsbl.prom.$$ /var/lib/node_exporter/textfile/dnsbl.prom# Alertmanager rule
- alert: IPBlacklisted
expr: dnsbl_listings > 0
for: 10m
labels:
severity: critical
annotations:
summary: "IP {{ $labels.ip }} listed on {{ $value }} blacklists"What to do when an alert fires
- Confirm. Some blacklists have false positives; some return temporary listings that resolve themselves in 30 minutes. Wait for the
for: 10mwindow. - Identify the specific list. Spamhaus ZEN is actually five sub-lists (SBL, XBL, PBL, CSS, DROP). The return code tells you which one:
127.0.0.2= SBL,.4= XBL,.10/.11= PBL,.3= CSS. - Check the reason. Most lists publish a lookup page with evidence and instructions. For dedicated IPs with legitimate mail, a delisting request is usually approved within 24 hours.
- Pause sending from the listed IP until delisting, unless you are warming a shared pool where the listing is not about your traffic.
Shared IP caveat
If you send through a shared ESP pool, you cannot control listings on the upstream IPs. Your ESP does. But you should still monitor — if the shared pool gets listed, you need to know immediately so you can pause cadence or rotate to a different pool.
For dedicated IPs, a listing is your problem and you have to act.
FAQ
How many blacklists is enough to monitor?
What about MXToolbox / MultiRBL.valli.org?
What if my domain, not my IP, is blacklisted?
acme.io.dbl.spamhaus.org. If it returns a 127.0.1.x address, the domain is listed. Follow the same pattern as IP-based checks.