seanmonstar/warp · warning

not_found

Error message

not_found

What it means

warp::host::exact (src/filters/host.rs:28) rejects with 404 not_found when the request's Authority (Host header / authority) does not equal the expected string. The filter extracts the authority via host::optional() and, unless it matches exactly, returns reject::not_found(). Panics at construction if the expected string is not a valid authority.

Solutions

  1. Match the exact authority including port: warp::host::exact("example.com:443") when requests carry a port
  2. Configure the reverse proxy to pass the original Host through (nginx: proxy_set_header Host $host;)
  3. Use host::optional() yourself and compare only the host part if you want port-insensitive matching
  4. Remove or relax the host filter if vhost discrimination is not actually needed
  5. Test with curl -H "Host: example.com" to confirm the filter matches the intended value

Example fix

// before
let routes = warp::host::exact("example.com").and(api());
// after (accept both bare host and host:port)
let routes = warp::host::optional()
    .and_then(|opt: Option<http::uri::Authority>| async move {
        match opt {
            Some(a) if a.host() == "example.com" => Ok(()),
            _ => Err(warp::reject::not_found()),
        }
    })
    .untuple_one()
    .and(api());
Defensive patterns

Strategy: validation

Validate before calling

// verify what authority the server actually receives
curl -s -o /dev/null -D - -H "Host: example.com:443" https://api.example.com/route
// compare against the exact string passed to warp::host::exact

Type guard

fn host_matches(authority: &http::uri::Authority, expected_host: &str) -> bool {
    authority.host() == expected_host
}

Try / catch

let route = warp::host::exact("example.com")
    .and(api())
    .recover(|rej: warp::Rejection| async move {
        if rej.is_not_found() {
            Ok(warp::reply::with_status("unknown host", warp::http::StatusCode::NOT_FOUND))
        } else {
            Err(rej)
        }
    });

Prevention

When it happens

Trigger: A request arrives whose Host header (including port) differs from the value passed to warp::host::exact("api.example.com") — e.g. host::exact("example.com") receiving "example.com:443", an internal IP, localhost, or a vhost mismatch through a reverse proxy.

Common situations: Behind nginx/ALB that forwards the upstream host instead of the original one (missing proxy_set_header Host); testing locally with localhost when the filter expects the production domain; forgetting the port in the expected authority; TLS/SNI mismatch; migrating domains without updating the filter.

Related errors


AI-assisted analysis of seanmonstar/warp@ff34d7213e (2026-09-09). Data as JSON: /api/errors/c5c358de4f5f02e0. Report an issue: GitHub.

Appendix: source

Thrown at src/filters/host.rs:28

/// host and port) in the request.
///
/// Authority is specified either in the `Host` header or in the target URI.
///
/// # Example
///
/// ```
/// use warp::Filter;
///
/// let multihost =
///     warp::host::exact("foo.com").map(|| "you've reached foo.com")
///     .or(warp::host::exact("bar.com").map(|| "you've reached bar.com"));
/// ```
pub fn exact(expected: &str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
    let expected = Authority::from_str(expected).expect("invalid host/authority");
    optional()
        .and_then(move |option: Option<Authority>| match option {
            Some(authority) if authority == expected => future::ok(()),
            _ => future::err(reject::not_found()),
        })
        .untuple_one()
}

/// Creates a `Filter` that looks for an authority (target server's host
/// and port) in the request.
///
/// Authority is specified either in the `Host` header or in the target URI.
///
/// If found, extracts the `Authority`, otherwise continues the request,
/// extracting `None`.
///
/// Rejects with `400 Bad Request` if the `Host` header is malformed or if there
/// is a mismatch between the `Host` header and the target URI.
///
/// # Example
///
/// ```

View on GitHub (pinned to ff34d7213e)