Choosing and Combining DNSBL and URIBL Lists Safely: Weighted Scoring Instead of Hard Rejects
A single reject_rbl_client line is a single point of failure that silently 5xxes real mail. Here is how to treat blocklists as weighted signals in Postscreen and rspamd, plus the DNS plumbing that quietly breaks all of it.
EvilMail TeamJuly 20, 202611 min read
The problem with one reject line
This line appears in more Postfix configs than any other spam control, and it is a liability dressed as protection:
It works about 99% of the time. The other 1% is where you lose the mail that matters and never find out. A shared SendGrid or EC2 IP that a spammer touched an hour before your customer's invoice went out. A small business on a dynamic range that is listed in Spamhaus PBL but legitimately runs its own MTA. A university forwarder relaying alumni mail through an address that picked up a transient listing. In every case the connection gets a 5xx, the bounce lands in the sender's logs, and your postmaster mailbox stays empty. Silent false positives are the most expensive failures in mail precisely because nobody is measuring them.
Then there is the systemic risk. A single list is a single point of failure. DNSBLs have a well-known catastrophic failure mode: the zone breaks and starts answering 127.0.0.2
DNSBL & URIBL Weighted Scoring: Stop Hard-Rejecting Legit Mail — EvilMail Blog
for
every
query — the "listed everything" or return-all event. It has happened to major lists after domain expirations and wildcard misconfigurations. If your one reject line trusts that zone, you reject the entire internet until someone notices. And in mid-2024 SORBS was shut down entirely by Proofpoint; every config still pointed at
dnsbl.sorbs.net
is now querying a dead list and either failing open or, worse, catching a parked wildcard.
The fix is a change in stance. A blocklist is not a judge handing down a verdict. It is a witness giving probabilistic evidence. You reject only on the few witnesses that testify to observed abuse, and you let the noisy ones vote into a score.
What each list actually flags
You cannot weight what you do not understand, so start with the taxonomy. A DNSBL rates the reputation of the connecting IP — the host currently talking to your MTA. A URIBL or RHSBL rates a domain or URL found inside the message: links in the body, the envelope sender's domain, the HELO name. That distinction dictates where each list can even run, which matters later.
The lists worth knowing as of 2026:
Spamhaus ZEN (zen.spamhaus.org) — a composite of SBL, CSS, XBL and PBL in one lookup. The return code tells you which sub-zone matched, and those mean wildly different things. Treating "got an answer" as "reject" throws away the entire point of ZEN.
Barracuda (b.barracudacentral.org) — solid IP reputation, requires free registration of your resolver's IP. Good as a moderate-weight signal.
SpamCop (bl.spamcop.net) — fast and aggressive, driven by spamtrap hits and user reports. Higher false-positive rate and short TTLs. A great scoring signal and a terrible reject signal.
UCEPROTECT — Level 1 (dnsbl-1.uceprotect.net) is per-IP and usable at low weight. Level 2 and 3 list entire ASNs and ranges as collateral pressure. Never hard reject on L2/L3; arguably never even score them.
DNSWL (list.dnswl.org) — the inverse of everything above. An allowlist you subtract with, so a trusted sender can absorb a stray hit.
On the URI side: Spamhaus DBL (dbl.spamhaus.org), SURBL (multi.surbl.org) and URIBL.com (multi.uribl.com). These need the message body, so they live in your content filter, not at the connection.
Two tiers: reject versus score
The whole framework fits in one decision. Every list falls into one of two tiers.
Tier 1 — hard reject. High confidence, evidence-backed; the listing implies observed abuse rather than mere policy:
Spamhaus SBL — 127.0.0.2
Spamhaus CSS — 127.0.0.3 (compromised hosts and snowshoe)
Spamhaus XBL — 127.0.0.4 through 127.0.0.7 (exploited machines, CBL data)
Spamhaus DROP/EDROP — 127.0.0.9
DBL spam-domain codes — 127.0.1.2 through 127.0.1.6
Tier 2 — score only. Policy-based, aggressive, or heuristic; legitimate senders live here:
Spamhaus PBL — 127.0.0.10 and 127.0.0.11 (end-user and dynamic space that policy says should not send direct-to-MX; a self-hosted MTA on a home connection lands here and is not a spammer)
SpamCop, Barracuda, UCEPROTECT L1
DBL abused-redirector codes — 127.0.1.102 and up (a legit URL shortener someone pointed at spam)
The critical nuance: the same ZEN lookup can be Tier 1 or Tier 2 depending on the return code. You branch on the octet, not on the mere presence of an answer.
Weighted scoring in Postscreen, before smtpd
Postscreen is the most underused thing Postfix ships. It runs at the connection layer, before smtpd, and it does weighted DNSBL scoring natively — cheaply, on the IP alone, before you spend a content-filter cycle. This is where Tier-1 IP rejects and cheap Tier-2 scoring belong.
The *N suffix is the weight; a negative weight subtracts. The =127.0.0.[2..7] filter means the site only contributes when the answer falls in that range — that is how you give ZEN's abuse codes a weight of 3 while giving its PBL codes a weight of 1, from the same zone.
The arithmetic is the point:
A host in PBL only: +1. Below threshold 3 → passes. A self-hosted small-business MTA is not rejected for sitting on a residential range.
PBL + SpamCop + Barracuda: 1 + 1 + 2 = 4. At or above 3 → rejected. Three independent witnesses agree.
A DNSWL-trusted sender that happens to be on SpamCop: -2 + 1 = -1. Below threshold, and below whitelist_threshold, so it is actively trusted → passes even while listed.
postscreen_dnsbl_whitelist_threshold = -2 also lets a strongly allowlisted host skip the expensive deep protocol tests entirely. Start with postscreen_dnsbl_action = ignore (log only, reject nothing) for a full week, watch the scores in your logs, and only switch to enforce once the distribution matches reality.
Weighted scoring in rspamd, unified with everything else
Postscreen sees only the connecting IP and the HELO/envelope. It cannot check URIBLs, because those need the message body it never reads. That is what rspamd is for, and rspamd is where the model gets genuinely powerful: DNSBL and URIBL hits become symbols in the same score as SPF, DKIM, Bayes and content rules.
Because these land in the unified score, a URIBL hit worth 4.0 on a link in the body plus an R_SPF_SOFTFAIL worth 2.0 can combine to cross your reject action even though neither alone would. That is the additive model doing exactly what a single binary gate cannot. rspamd's actions — greylist, add header, rewrite subject, reject — are the endgame thresholds. If you use Spamhaus's paid data, DQS keys plug straight in here by swapping the zone names.
The DNS plumbing that silently breaks all of this
This is the section that separates people who have run blocklists in production from people who have read a tutorial. DNSBL lookups are DNS queries, and where you send them decides whether any of the above works at all.
Public resolvers — 8.8.8.8, 1.1.1.1, Quad9 — are rate-limited or outright blocked by Spamhaus, Barracuda and others. Their free data licences forbid use behind large shared resolvers, and above roughly 100k queries a day your queries get throttled or refused. The symptom is brutal precisely because it is quiet: you get NXDOMAIN or an error, rspamd and Postscreen read that as "not listed," and your filtering fails open with zero errors in your logs. Everything looks healthy while nothing is being blocked.
Run a local recursive resolver on the mail host. Unbound is the standard answer:
bash
apt install unbound
# point /etc/resolv.conf at 127.0.0.1, then verify:
dig +short 2.0.0.127.zen.spamhaus.org
# MUST return 127.0.0.2 (and often .4, .10) — this is the test entry
dig +short 2.0.0.127.dnsbl-1.uceprotect.net
# MUST return a 127.0.0.x hit — proves a second list resolves too
Reverse the octets when you build the query: to test 127.0.0.2 you look up 2.0.0.127.<zone>. If 2.0.0.127.zen.spamhaus.org returns nothing, your lookups are broken and you are filtering nothing, no matter how elegant your weights are.
Once your volume outgrows the free tier, move to Spamhaus DQS (Data Query Service) with keyed zones: <key>.zen.dq.spamhaus.net, <key>.dbl.dq.spamhaus.net. Same return codes, higher limits, fresher data.
Tuning against your own mail, not vibes
Weights are not universal constants you copy off a blog. A B2B host that only ever receives from established business senders can run a tight threshold; a consumer host taking mail from everyone's cousin's home server has to be far more forgiving of PBL. Set the numbers against your traffic:
Run log-only (postscreen_dnsbl_action = ignore, rspamd actions high) for at least a week.
Grep the Postscreen logs and rspamd_history for the hit distribution: how often does each list fire, and on whom?
Take a corpus of known-good senders and compute the real false-positive rate per list before you trust it with weight.
Greylist the ambiguous middle band instead of rejecting it — a temporary 4xx costs a legit sender a few minutes and costs a spam cannon its whole run.
Re-check quarterly. Lists change coverage, merge, and die (see SORBS).
Checklist before you ship
Local recursive resolver (unbound) confirmed on the mail host — not8.8.8.8 or 1.1.1.1.
dig +short 2.0.0.127.zen.spamhaus.org returns 127.0.0.2. Lookups are actually alive.
Only Tier-1 return codes (SBL/CSS/XBL/DROP/DBL-spam) on hard reject; PBL and friends are score-only.
DNSWL (or your own allowlist) present with a negative weight so a trusted sender absorbs a stray hit.
Postscreen ran a full log-only week before action = enforce.
URIBLs run in rspamd against the message body, never at the connection layer.
A cron monitor tests a known-good control IP against every list; if a list ever returns positive for it, that list is in a return-all event — auto-disable it and fail open.
Spamhaus DQS keys in place if you exceed the free query limits.
Per-list weights documented in the config, so the next engineer knows why SpamCop is a 1 and not a 7.
The one-line reject felt safe because it was simple. Simple is not the same as correct. A weighted, allowlist-offset, return-code-aware score is more config to write once — and it is the difference between "I block spam" and "I block spam without quietly bleeding legitimate mail I can never account for."