hatoo/oha · warning · anyhow::Error

DNS: failed to load /etc/resolv.conf

Error message

DNS: failed to load /etc/resolv.conf: {err}

What it means

system_resolv_conf builds the DNS resolver configuration by reading the system's /etc/resolv.conf via hickory-resolver. If read_system_conf fails (file missing, unreadable, or unparseable — note Android/Termux is unsupported since hickory 0.26), the error is wrapped with the 'DNS: failed to load /etc/resolv.conf' prefix and passed to fallback_resolver_config, which may proceed with a fallback resolver or surface the error.

Solutions

  1. Ensure /etc/resolv.conf exists and is readable (e.g. create one with `nameserver 8.8.8.8` in containers).
  2. Fix file permissions on /etc/resolv.conf.
  3. On Android/Termux, note hickory 0.26 no longer supports it; run on a supported platform or supply a fallback config path.
  4. Inspect the wrapped `{err}` message for the underlying cause.

Example fix

// before
# scratch container without resolv.conf
oha https://example.com
// after
RUN echo 'nameserver 8.8.8.8' > /etc/resolv.conf
oha https://example.com
Defensive patterns

Strategy: fallback

Validate before calling

use std::path::Path;
if !Path::new("/etc/resolv.conf").exists() {
    eprintln!("Warning: /etc/resolv.conf missing; DNS resolution may fail");
}

Try / catch

match system_resolv_conf() {
    Ok((config, opts)) => /* build resolver */,
    Err(e) if e.to_string().contains("failed to load /etc/resolv.conf") => {
        // use a hardcoded ResolverConfig with public DNS, e.g. 8.8.8.8
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Container images or minimal systems without /etc/resolv.conf; restrictive file permissions on resolv.conf; running on Android/Termux where hickory 0.26+ removed that capability; a malformed resolv.conf that hickory cannot parse.

Common situations: Scratch/distroless Docker images lacking resolv.conf; sandboxed CI runners; Android environments after upgrading hickory-resolver.

Understand the failure class

Background: "Config file not found": what it means and how to fix it in docker-sync, Maven, Vagrant, Turborepo and other tools — this error's family across 60 libraries.


AI-assisted analysis of hatoo/oha@4efba2d113 (2026-09-09). Data as JSON: /api/errors/e73a3cc9c38f8e9f. Report an issue: GitHub.

Appendix: source

Thrown at src/main.rs:910

    let (res, print_config) = data_collect_future.await;

    printer::print_result(print_config, start, &res, duration)?;

    if let Some(db_url) = opts.db_url {
        eprintln!("Storing results to {db_url}");
        db::store(&client, &db_url, start, res.success(), run as i64)?;
    }

    Ok(())
}

pub(crate) fn system_resolv_conf() -> anyhow::Result<(ResolverConfig, ResolverOpts)> {
    // Termux (https://github.com/termux/termux-app) is no longer supported:
    // hickory-resolver 0.26 removed `parse_resolv_conf` on Android, so we can
    // no longer read `$PREFIX/etc/resolv.conf` via hickory.
    match hickory_resolver::system_conf::read_system_conf() {
        Ok(conf) => Ok(conf),
        Err(err) => fallback_resolver_config(anyhow::anyhow!(
            "DNS: failed to load /etc/resolv.conf: {err}"
        )),
    }
}

fn fallback_resolver_config(err: anyhow::Error) -> anyhow::Result<(ResolverConfig, ResolverOpts)> {
    // Notify the user that we had to fall back to a default resolver configuration.
    eprintln!("{err}");

    let config = ResolverConfig::default();
    let opts = ResolverOpts::default();
    Ok((config, opts))
}

enum WorkMode {
    Debug,
    FixedNumber {
        n_requests: usize,

View on GitHub (pinned to 4efba2d113)