stalwartlabs/stalwart · critical
Failed to build DNS resolver
Error message
Failed to build DNS resolver
What it means
This panic occurs when the standard (non-DNSSEC) hickory resolver wrapped in MessageAuthenticator cannot be built during resolver initialization. MessageAuthenticator::new(config, opts) uses .expect("Failed to build DNS resolver"), so any invalid resolver configuration aborts instead of returning an error. It indicates the ResolverConfig or ResolverOpts supplied for plain DNS are invalid.
Solutions
- Validate the nameserver IPs/ports and resolver options in the smtp.resolver config section
- Revert to default resolver options (remove custom timeout/cache/protocol overrides) to isolate the offending value
- Test resolution with a minimal config using system resolvers
- Check hickory-resolver changelogs after upgrades for option semantics changes
- Replace .expect() with proper error propagation so startup reports the underlying cause
Example fix
// before
MessageAuthenticator::new(config, opts).expect("Failed to build DNS resolver"),
// after
MessageAuthenticator::new(config, opts)
.map_err(|e| anyhow!("Failed to build DNS resolver: {e}"))?, Defensive patterns
Strategy: validation
Validate before calling
// sanity-check the plain DNS config before constructing the resolver
fn validate_dns_options(opts: &ResolverOpts) -> Result<(), String> {
if opts.timeout.is_zero() {
return Err("resolver timeout must be > 0".into());
}
Ok(())
} Try / catch
// The constructor panics via expect; guard at startup:
let resolvers = std::panic::catch_unwind(|| build_resolvers(&config, &opts))
.map_err(|_| anyhow!("DNS resolver init panicked — check resolver config"))?; Prevention
- Double-check nameserver addresses and ports in the resolver config for typos
- Avoid exotic ResolverOpts overrides unless documented for your hickory version
- Validate all TOML config values that feed into ResolverConfig/ResolverOpts
- Run a config self-test at startup that resolves a known name before serving traffic
When it happens
Trigger: Constructing the resolver struct where MessageAuthenticator::new(config, opts) fails — a ResolverConfig with malformed nameserver addresses/ports, or ResolverOpts values hickory rejects at build time (invalid timeouts, cache sizes, or protocol settings).
Common situations: Typo'd or malformed nameserver IP in the smtp.resolver config section; invalid resolver options (protocol, timeout, cache) in configuration; hickory version upgrades changing accepted option semantics; corrupted config values loaded from TOML.
Related errors
- Failed to build DNSSEC resolver
- Failed to parse calendar template
- Failed to build the TLS client configuration
- Failed to decode base64
- Failed to delete keys
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/5992f8b1eee2e87d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/common/src/config/smtp/resolver.rs:348
}
}
impl Default for Resolvers {
fn default() -> Self {
let (config, opts) = match read_system_conf() {
Ok(conf) => conf,
Err(_) => (
ResolverConfig::udp_and_tcp(&CLOUDFLARE),
ResolverOpts::default(),
),
};
let config_dnssec = config.clone();
let mut opts_dnssec = opts.clone();
opts_dnssec.validate = true;
Self {
dns: MessageAuthenticator::new(config, opts).expect("Failed to build DNS resolver"),
dnssec: DnssecResolver {
resolver: TokioResolver::builder_with_config(
config_dnssec,
TokioRuntimeProvider::default(),
)
.with_options(opts_dnssec)
.build()
.expect("Failed to build DNSSEC resolver"),
},
dnssec_available: true,
}
}
}
impl Display for Policy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("version: STSv1\r\n")?;
f.write_str("mode: ")?;View on GitHub (pinned to e962003857)