黑名单监控:脚本、API 与 Webhook 告警
黑名单监控是指按计划在多个 DNS 黑名单(DNSBL)中查询您的发信 IP 和域名,并且只在结果发生变化时发出告警。在被列入的当下就发现问题,而不是等客户反映邮件被退回才知道,就能在损害扩大之前查明原因并申请解除。
实际可行的方法有两种:自己运行一个小型的 dig 加 cron 脚本,或者通过 API 和 Webhook 把检查交给服务来完成。无论哪种方式,有两个细节决定监控结果是否可信:通过公共解析器发出的查询会被 Spamhaus 等名单拒绝,并产生误导性的应答;而错误码绝不能被计为列入。
本文引用的名单运营方规则,依据的是其截至 2026 年 11 月的官方页面。政策会变化,依赖某套配置之前请先确认。关于 DNSBL 工作原理的背景知识,请参阅什么是 DNSBL(RBL)。
确定监控对象和要查询的名单
首先列出您真正需要监控的对象。
- 发信 IP 地址。即您的邮件服务器或邮件服务商实际发信所用的地址。网站和邮件往往从不同的机器发出,因此请在您所发邮件的 Received 头中确认地址。如果您通过 IPv6 发信,也要监控这些地址。
- 域名。发件人(From)地址中的域名,以及邮件中链接所含的所有域名。域名要在域名黑名单中检查,这类名单与 IP 名单是分开的。
查询格式由 RFC 5782 定义。对于 IPv4,将八位组倒序后加上名单的区域名,因此在某份名单上查询 192.0.2.99 时,写作 99.2.0.192 后接区域名。对于 IPv6,地址写成 32 个十六进制半字节并倒序排列。域名则直接加在区域名前面。被列入的条目会返回 A 记录,按惯例位于 127.0.0.0/8 范围内;未被列入的则返回 NXDOMAIN。RFC 还指出,名单应发布说明列入原因的 TXT 记录。
请同时监控多份名单,而不是只看一份,因为各收件方使用的名单并不相同。每个返回码的含义因运营方而异;我们的DNSBL 返回码指南介绍了常见的返回码。
为什么公共解析器会给出错误的应答
自建监控中一个常见的错误,是通过 Google Public DNS(8.8.8.8)或 Cloudflare(1.1.1.1)等公共解析器发送查询。Spamhaus 官方的 DNSBL 使用 FAQ 解释了这样做的后果:
- 免费使用 Spamhaus DNSBL 的前提是:用于非商业用途,并且查询不是来自公共或开放解析器,也不是来自使用通用反向解析的 IP。
- 通过 Google Public DNS 等免费开放解析器查询,大多数情况下会得到“未列入”(NXDOMAIN)的回复。也就是说,被列入的 IP 可能看起来是干净的。
- 通过公共解析器查询还可能收到 127.255.255.254。这是错误码,而不是列入。同样,127.255.255.252 表示 DNSBL 名称拼写错误,127.255.255.255 表示查询次数过多。Spamhaus 声明,不得据这些代码认为所查询的对象已被列入。
- 会改写 NXDOMAIN 应答的解析器会返回一个 IP 地址,导致您永远看不到正确的 NXDOMAIN。
该 FAQ 的首要建议是自行运行 DNS 解析器。对于必须使用公共解析器的用户,Spamhaus 推荐其 Data Query Service(DQS);免费范围请查看其条款。FAQ 还建议使用 dig txt whoami.fastly.net 查看您实际使用的是哪个解析器。
RFC 5782 还提供了一个内置的健全性检查:IPv4 名单必须包含 127.0.0.2 的条目,且不得包含 127.0.0.1,客户端应定期检查这些测试条目,以确认名单正常运行。在 Spamhaus 的示例中,2.0.0.127.zen.spamhaus.org 返回 127.0.0.10、127.0.0.4 和 127.0.0.2,而 1.0.0.127.zen.spamhaus.org 返回 NXDOMAIN。监控程序应首先进行这项测试,如果失败,则丢弃本次运行的结果。
方案 1:dig 加 cron 脚本
下面是一个最简脚本,用于在 Spamhaus ZEN 和 SpamCop(bl.spamcop.net,SpamCop 的文档说明被列入的 IP 会应答 127.0.0.2)中检查一个 IPv4 地址。请替换成您自己的地址和区域名。
#!/bin/sh
# 查询自己的递归解析器(例如同一主机上的 unbound),绝不使用公共解析器
RESOLVER=127.0.0.1
IP=192.0.2.10
ZONES="zen.spamhaus.org bl.spamcop.net"
REV=$(echo "$IP" | awk -F. '{print $4"."$3"."$2"."$1}')
q() {
out=$(dig +time=3 +tries=1 @"$RESOLVER" "$1" A) || { echo ERR; return; }
case "$out" in
*"status: NOERROR"*) a=$(printf '%s\n' "$out" | awk '$4=="A"{print $5}'); echo "${a:-NX}" ;;
*"status: NXDOMAIN"*) echo NX ;;
*) echo ERR ;;
esac
}
for Z in $ZONES; do
# 如果 127.0.0.2 测试条目没有解析为列入代码,则不信任本次运行结果
case "$(q "2.0.0.127.$Z")" in
127.255.255.*|NX|ERR) echo "$Z ERROR test-entry"; continue ;;
127.*) ;;
*) echo "$Z ERROR test-entry"; continue ;;
esac
A=$(q "$REV.$Z")
case "$A" in
NX) echo "$Z CLEAN" ;;
ERR) echo "$Z ERROR no-answer" ;;
127.255.255.*) echo "$Z ERROR $A" ;;
127.*) echo "$Z LISTED $A" ;;
*) echo "$Z ERROR unexpected $A" ;;
esac
done脚本的处理逻辑:
- 只有 NXDOMAIN 状态才算干净,127.255.255.x 是错误,其他任何 127.x 都是列入。127.0.0.0/8 以外的任何值都视为异常,例如解析器改写了应答。
- 超时会使 dig 以非零状态退出,而 SERVFAIL 和 REFUSED 的退出状态为 0。这些情况都不会被视为干净,脚本会记录为错误。
- 返回码因名单而异。例如,有些名单用 127.0.0.1 表示查询被拒绝,因此每添加一份名单,都请阅读其文档,并在需要时扩充 case 语句。
然后让 cron 把每次运行的结果与上一次比较,只在有变化时发送邮件:
# Example: run hourly, send mail only when the output differs from the last run
0 * * * * /usr/local/bin/dnsbl-watch.sh > /var/tmp/dnsbl.now 2>&1; cmp -s /var/tmp/dnsbl.now /var/tmp/dnsbl.prev || mail -s "DNSBL status changed" you@example.com < /var/tmp/dnsbl.now; mv /var/tmp/dnsbl.now /var/tmp/dnsbl.prev每小时一次的间隔只是示例。Spamhaus 为低查询量的非商业用途提供免费访问,超过其查询量阈值的客户端需要使用付费方案;随着监控 IP 数量的增加,请查看各名单的条款。FAQ 还建议把命令多试几次,确认结果保持一致。
方案 2:DNSBL Checker 批量 API 与 Webhook
如果您不想自己维护解析器和各名单的解析规则,可以让服务来执行检查。下面介绍本站的 v1 API,内容仅限于服务器端实现实际支持的功能。参考文档位于 /en/api-docs,密钥在会员页面 /account 上签发。注册只需一个电子邮件地址,登录时使用通过邮件发送的链接。
认证与限制
- 密钥以
dnsbl_开头,只在签发后立即显示一次;服务器只保存其哈希值。重新签发密钥会使之前的密钥失效。 - 每次请求都在
X-API-Key头中发送密钥。仅支持 HTTPS;普通的 http:// 会被重定向到 https://,因此请从一开始就调用 https://。 - 限制为每天 1,000 次检查(一个目标计为一次)、每个批次 100 个目标、最多注册 50 个监控项。超过每日限制时,会返回
429,并附带daily_limit_exceeded和resets_at时间戳。
批量检查
向 POST /api/v1/batch 发送由域名或 IP 地址组成的 targets 数组:
curl -X POST https://dnsblchecker.com/api/v1/batch \
-H "X-API-Key: $DNSBL_API_KEY" -H "Content-Type: application/json" \
-d '{"targets":["example.com"]}'如果有任何目标仍需检查,响应为 202,并附带 batch_id。每隔几秒轮询一次 GET /api/v1/batch/{batch_id},直到 status 变为 done(节选示例;其中的数字和名单名称仅作说明,并非真实结果):
{
"batch_id": 1, "status": "done", "created": "2026-11-03 09:10:41", "count": 1, "pending": 0,
"results": [
{
"target": "example.com", "type": "domain",
"status": "done", "verdict": "listed",
"checked_at": "2026-11-03 09:12",
"lists_checked": 80, "listed_count": 1, "error_count": 4,
"listed_on": [
{ "list": "ZapBL RHSBL", "zone": "rhsbl.zapbl.net", "response": "127.0.0.2" }
]
}
]
}- 只要至少有一份名单报告列入,
verdict就为listed,否则为clean。超时的名单,或以 127.255.255.x 等错误码应答的名单,会计入error_count,绝不会算作列入。 - 如果存在过去 30 天内保存的结果,批量检查会直接返回该结果。进行定期监控时,请务必查看
checked_at。 - 格式错误的目标会连同原因一起出现在
rejected中,不计入次数。如果传入 URL,则只使用其中的主机名。192.0.2.0/24 等仅用于文档的地址段会被拒绝,因此请用真实的发信 IP 进行测试。
Webhook 监控
通过 POST /api/v1/monitors 注册 target 和 callback_url。此后,每个已注册的目标每天都会在不使用缓存的情况下检查一次(首次检查在注册后约一天内运行),并且只在首次检查(monitor.initial)时,以及判定结果在 listed 与 clean 之间切换(monitor.status_changed)时,向您的 URL 发送 JSON 格式的 POST。
curl -X POST https://dnsblchecker.com/api/v1/monitors \
-H "X-API-Key: $DNSBL_API_KEY" -H "Content-Type: application/json" \
-d '{"target":"example.com","callback_url":"https://your-app.example/hooks/dnsbl"}'- 响应中的
signing_secret只显示一次。每次推送都带有X-DNSBL-Signature: sha256=...头,其值是以该密钥对原始请求体计算的 HMAC-SHA256。请在您这一端进行验证。 callback_url必须使用 443 端口上的 https,并解析到公网 IP;不跟随重定向,超时时间为 10 秒,因此请把耗时的处理放入队列,并尽快返回响应。- 如果您的端点返回
2xx以外的状态,或连接失败,会在下一次每日检查时重新推送。失败原因会以last_delivery_error显示在GET /api/v1/monitors中,使用DELETE /api/v1/monitors/{monitor_id}可删除监控项。
import hmac, hashlib
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, request.headers.get("X-DNSBL-Signature", ""))告警设计:频率、多份名单与误报
只有在几个月后大家仍会阅读告警,监控才有意义。频繁触发的告警很快就会被忽视。
频率
- 把查询频率和通知频率分开。按计划查询,但只在状态变化时通知。每天一封“一切正常”的邮件,会把真正重要的那条告警淹没。
- RFC 5782 指出,名单的 TTL 应反映名单变化的速度,垃圾邮件来源名单的 TTL 可以是几分钟。选择检查间隔时,要考虑到有些列入出现和过期都很快。
- 大量查询可能违反名单的使用条款,或者让您收到 127.255.255.255(查询过多),而不是真实的应答。
多份名单
- 在每条告警中写明名单名称。各名单被收件方采用的程度不同,因此可以对收件方最看重的名单立即告警,其余的汇总成摘要。
- 上述 Webhook 只在判定结果在 listed 与 clean 之间切换时触发。如果某个目标已被列入,之后又出现在第二份名单上,则不会发送事件。要发现这种情况,请关注
GET /api/v1/monitors中的last_listed_count,或运行一次批量检查并查看listed_on。
误报和暂时性应答
- 把错误与列入分开记录。Spamhaus 警告运营者不要根据其错误码拒收或过滤邮件;监控也是同样的道理。超时或 127.255.255.x 的应答绝不应触发列入告警。
- 为避免对单次异常结果做出反应,只在连续两次结果相同时才告警,或者在发送任何告警之前,过几分钟再查询一次。
- 如果错误持续出现,应怀疑解析器发生了变化或名单方有变动,并首先查看测试条目的结果。
- 家庭宽带 IP 出现在 Spamhaus PBL 上属于政策性列入,而不是指控:它表示该地址不应直接发信。请按名单分别确定哪些情况需要告警(参阅Spamhaus 解除)。
检测到列入后该怎么做
- 确认列入。在运营方自己的查询页面上检查,记下名单名称、时间以及显示的原因。
- 先消除原因。排查被盗用的账户、被滥用的网页表单、被感染的机器、邮件列表维护不善,以及反向解析(PTR)或 HELO 名称不一致等问题。在原因仍然存在时申请解除,通常会导致再次被列入。
- 按照该名单的解除流程办理。是否解除由各名单运营方决定,提交申请并不代表一定会被解除。如果 IP 不在您的控制之下(例如共享主机),请联系主机商或 ISP。
- 让监控确认结果。收件方也会运行自己的过滤器,因此在判定结果恢复为 clean 之后,也请继续关注投递情况。
如果只想临时查一下某个 IP 或域名,请使用黑名单检测工具。如果追查原因或处理申请超出了您的能力范围,可以向我们咨询。邮件被退回时的完整排查步骤,请参阅邮件因黑名单被拦截。
常见问题
我通过 8.8.8.8 查询,没有发现列入,这样就没问题了吗?
不一定。Spamhaus 的 FAQ 指出,通过 Google Public DNS 等免费开放解析器查询,大多数情况下会得到“未列入”(NXDOMAIN)的回复,因此被列入的 IP 可能看起来是干净的。请从您自己的解析器查询,或使用运营方的查询页面。
收到 127.255.255.254,是说明我被列入了吗?
不是。这是通过公共或开放解析器查询时返回的错误码,Spamhaus 声明它并不表示对象已被列入。请将其记录为错误,不要发送列入告警。
可以每天调用批量 API 来代替监控吗?
批量检查可能返回最多 30 天前保存的结果,因此不一定是当天的状态。请查看 checked_at,或者使用每天都会重新检查的 Webhook 监控(POST /api/v1/monitors)。
告警一触发就应该申请解除吗?
请先消除原因。在问题仍然存在时提出申请,通常会导致再次被列入。决定权始终在名单运营方。
参考的官方页面
本文中的事实已在发布时对照以下官方页面进行核实。相关政策和行为可能随时变更,恕不另行通知,在依赖重要信息之前,请先查看官方页面。
- RFC 5782: DNS Blacklists and Whitelists
- Spamhaus FAQ: DNSBL Usage
- Spamhaus: Using our public mirrors? Check your return codes now
- SpamCop FAQ: How do I configure my mailserver to reject mail based on the blocklist?
相关指南
如何解读 DNSBL 返回码:RFC 5782 中 127.0.0.2 的含义,Spamhaus、SpamCop 和 UCEPROTECT 的返回码,错误码,以及 NXDOMAIN 与 SERVFAIL 的区别。
被 Spamhaus 列入黑名单?了解您在哪个列表上(ZEN、PBL、XBL、CSS、SBL、DBL)、返回码的含义,以及每个列表对应的正确移除途径。
邮件被黑名单拦截?读懂退信代码,按正确顺序排查原因,判断哪些黑名单真正有影响,并以正确的方式申请移除。