If you run a small mail server set up in a normal way with something like spamhaus, you might have encountered something like this:

Nov 19 03:51:34 mail postfix/smtpd[3254413]: NOQUEUE: reject: RCPT from mail-lj1-f169.google.com[209.85.208.169]: 554 5.7.1 Service unavailable; Client host [209.85.208.169] blocked using zen.spamhaus.org; Error: open resolver; https://www.spamhaus.org/returnc/pub/35.80.40.11; from=<foo@example.org to=<bar@example.org> proto=ESMTP helo=<mail-lj1-f169.google.com>

Perhaps this is because you have something like this in your postfix config:

# Somewhere in /etc/postfix/main.cf
smtpd_client_restrictions = zen.spamhaus.org

Starting back in 2021, spamhaus began blocking open relays, and the way that this works was that by default, your mail would just get rejected because by default postfix will reject for any response from the server that isn’t NXDOMAIN. Note that not all ISP servers are marked as public servers / “open resolvers”, so you might not have noticed when this started happening.

So to start, let’s stop what is effectively “please fix your configuration” from breaking mail. We can do that by telling postfix that only specific responses (not just NXDOMAIN) should cause mail to get rejected

# Somewhere in /etc/postfix/main.cf
smtpd_client_restrictions = reject_rbl_client zen.spamhaus.org=127.0.0.[2..11]

That change will prevent spamhaus responses from rejecting mail on your server, but you still have the underlying problem that they’re sending you an error versus telling you something about the machine talking to your mail server.

Fixing that requires skipping your ISP’s DNS server and going directly to spamhaus. For me, that means making systemd-resolver (my system’s stub resolver) hit a local DNS server that performs recursive requests.

A good choice for a local recursive server with a decent default configuration is unbound.

By default, unbound will bind to 127.0.0.1:53 (systemd-resolved will bind to 127.0.0.53:53), which will not conflict with systemd-resolved. The next thing you need to do is to get systemd-resolved to start using unbound for those requests.

For me, adding some lines like this made systemd-resolved use unbound for specific domains. While we’re here, let’s add some other things that might end up breaking if we don’t resolve them directly.

# Somewhere in /etc/systemd/resolved.conf
DNS=127.0.0.1
Domains=~spamhaus.org. ~routeviews.org. ~mailspike.net. ~dkimwl.org. ~sorbs.net. ~senderscore.com. ~surbl.org. ~dnswl.org. ~uribl.com. ~uceprotect.net.

You can validate that it’s working as expected with: systemd-resolve --status. I see output like this in there:

  Current DNS Server: 127.0.0.1           
         DNS Servers: 127.0.0.1           
          DNS Domain: ~spamhaus.org       
                      ~routeviews.org    

The last thing to do is to validate that it’s sending the requests as expected. You can use systemd-resolve to see how a resolution gets treated.

For example, we see that the spamhaus domain ends up going through unbound:

$ systemd-resolve 2.0.0.127.zen.spamhaus.org
2.0.0.127.zen.spamhaus.org: 127.0.0.2
                            127.0.0.10
                            127.0.0.4

-- Information acquired via protocol DNS in 119.5ms.

But another domain goes through the link-specific DNS that hits the shared resolver:

$ systemd-resolve example.org
example.org: 93.184.216.34                     -- link: ens5

-- Information acquired via protocol DNS in 2.1ms.

And that’s all there is to it. Hopefully this saves you some time if you’ve run into the same problem.