Outbound delivery to .aero / .org / .email fails with "DNSSEC validation failed" — follow-up to #3316 (still present in v0.16.22)

Issue Description

Hello,

We run Stalwart Enterprise v0.16.22 (Linux, RocksDB, internal directory, DnsResolver = System). From 2026-10-12 to 2026-10-16 every outbound message to a domain under .aero, .org or .email stayed in the queue or bounced. Each attempt logged:

INFO MX record lookup failed (delivery.mx-lookup-failed) ... domain = "<recipient>.aero",
causedBy = DNS error (mail-auth.dns-error) { details = "DNS error: DNSSEC validation failed" }

It is the same defect as GitHub issue #3316 (closed by the bot so it could be triaged here) and hickory-dns#3963, and it is still present in v0.16.22.

What we confirmed

  • The recipient domains are unsigned and their DNS is healthy. BIND’s delv, run on the same host against the same upstream resolvers, returns “unsigned answer” (a correct proof of insecurity) for every affected domain.
  • The three TLDs are all mid KSK rollover (2 KSK + 3 ZSK). Their DNSKEY response is about 1,320 bytes, so it is truncated at the 1,232-byte EDNS buffer. .com (371 bytes) and .co.za (751 bytes) were unaffected.
  • /etc/resolv.conf lists three nameservers and concurrency was 2, which is the race described in #3316.
  • No package, configuration or binary change on our side preceded the failures.

Workaround applied

We set DnsResolver.concurrency = 1 and restarted, because the resolver settings are only read at startup. The next retry delivered within two minutes, and there have been no failures since.

Requests

  1. Force num_concurrent_reqs = 1 for the DNSSEC resolver in crates/common/src/config/smtp/resolver.rs, as #3316 suggests, so the default System resolver is not exposed.
  2. In crates/smtp/src/outbound/dane/dnssec.rs, mx_lookup uses the validating resolver whenever dnssec_available is true, even when the TLS strategy has DANE set to Optional. A validation failure on an MX lookup should fall back to the non-validating resolver (with DnssecStatus Indeterminate) unless DANE is Required. One resolver bug should not stop delivery to whole TLDs.
  3. Make the error say what failed (zone, record type, truncated response). As it stands it points the operator at the recipient’s DNS, which was correct.
  4. Rebuild the resolver on ReloadSettings, or have the admin UI say a restart is needed.

We can provide redacted log excerpts if useful.

Regards,
Aeroworks Tech Team

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

I don’t have confirmation in docs on whether DNS resolver reload requires a restart beyond what’s already confirmed by the user’s own testing. I have enough to answer with what’s documented plus acknowledging this is a bug report rather than a how-to question.

Given this is clearly a detailed bug report with specific source-code-level fixes requested, the appropriate response is to acknowledge it and confirm current documented behavior, while noting a human will follow up on the code-level requests.

Thanks for the detailed writeup. A few things confirmed against current docs and source:

  • DnsResolver.concurrency defaults to 2, and setting it to 1 is exactly the documented concurrency field. The docs don’t mention any live-reload behavior for DnsResolver settings, and the resolver options (including num_concurrent_reqs) are built once from the DnsResolver config at startup in crates/common/src/config/smtp/resolver.rs. There is no documented mechanism for ReloadSettings to rebuild the resolver, so your observation that a restart was required to apply the concurrency change matches current behavior, not an oversight on your part. See the DNS Resolver docs for the full field list.

  • In crates/smtp/src/outbound/dane/dnssec.rs, mx_lookup does indeed gate on self.core.smtp.resolvers.dnssec_available globally rather than per-TLS-strategy, so a validation failure propagates to MX lookups even when DANE isn’t required for that destination. That matches what you’re describing.

This is a source-level behavior report rather than something addressable via configuration, so I can’t give you a docs-based fix for items 1–3; they describe actual resolver/code behavior as currently implemented. Your workaround (global concurrency = 1 plus restart) is consistent with how the settings are read today. I’ve flagged this thread for human engineering follow-up given the specificity of your findings (truncated DNSKEY during KSK rollover exceeding the 1,232-byte EDNS buffer, and the concurrency race).

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

I think all the hickory Bugs related to DNSSEC errors are fixed in .26

0.16.26 is the one to move to, as @ideer2 says. 0.16.23 already limited the DNSSEC resolver to one request at a time (your first request), and 0.16.26 replaced that with a patched hickory-resolver that retries over TCP when a late UDP response comes back truncated, which is the race behind #3316. You can set concurrency back to what it was after upgrading.