Skip to content

Use DNS Blacklists in PHP

A DNS blacklist (DNSBL) is queried like any other DNS name: reverse the octets of the address you want to check, append the zone of the list, and resolve it. If the name resolves, the address is listed. No API keys, no HTTP client, no library — a resolver is all you need.

The snippet below checks one address against a list of DNSBL zones and stops at the first hit. It works from the CLI as well as from a web request.

This post was written in 2008 and the code has been corrected for modern PHP: the original called implode($pieces, $glue) with the arguments in the legacy order, which was deprecated in PHP 7.4 and removed in PHP 8.0.

The original list of blacklists has not aged well and has been trimmed here. Several of the zones it contained are gone — SORBS was shut down in 2024, NJABL in 2013, and the CBL was folded into the Spamhaus XBL — and a retired list is worse than no list: AHBL famously started answering every query positively when it closed in 2015, so anyone still querying it blocked all of their mail. Verify every zone you query, and be aware that Spamhaus requires a Data Query Service account once you query from a shared or high-volume resolver.

The Code

<?php
/*
 * Simple DNSBL check.
 * Author: René Moser
 */

// The address to check. 127.0.0.2 is the conventional test address that
// every well-behaved DNSBL reports as listed.
$ip = '127.0.0.2';

// One DNSBL zone per line.
$blacklists = file(__DIR__ . '/dnsbl.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

// A DNSBL is queried with the octets of the address in reverse order.
$reverse = implode('.', array_reverse(explode('.', $ip)));

foreach ($blacklists as $blacklist) {
    if (gethostbynamel($reverse . '.' . $blacklist)) {
        fwrite(STDERR, "listed on $blacklist\n");
        echo "1\n";
        exit(1);
    }
}

echo "0\n";
exit(0);

And the corresponding dnsbl.txt. Keep it short and keep it curated — a handful of lists you trust beats a hundred you have never checked:

zen.spamhaus.org
bl.spamcop.net
psbl.surriel.com
ix.dnsbl.manitu.net
dnsbl-1.uceprotect.net

zen.spamhaus.org is a combined zone and already covers the SBL, XBL and PBL, so there is no point in querying those three separately as well.

Things to Keep in Mind

  • The return code carries meaning. gethostbynamel() only tells you that a name resolved. Most lists encode why an address is listed in the returned 127.0.0.x address, and a few use different sub-lists with very different policies. If you act on a listing automatically, evaluate the returned address instead of treating any answer as a hit.
  • This only works for IPv4. An IPv6 address has to be expanded to its full nibble form and reversed nibble by nibble, the same way ip6.arpa lookups work. Many lists do not carry IPv6 data at all.
  • Do not reject on a single listing. Scoring several lists, as a spam filter does, is far more robust than letting any one of them decide.
  • Cache the result. Every check is a DNS query, and checking the same address on every page view is both slow and unfriendly to the list operators, who run their zones for free.