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.
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)
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
- Node id {node_id} exceeds {MAX_NODE_ID}, panicking to avoid
- Invalid system time, panicking to avoid data corruption
- unwrap_tls called on non-TLS acceptor
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/5992f8b1eee2e87d.
Report an issue: GitHub.