stalwartlabs/stalwart · error

StoreEvent::HttpStoreError

StoreEvent::HttpStoreError

Error message

Failed to fetch HTTP list

What it means

The HTTP-backed store (used e.g. for IP blocklists/allowlists) refreshed its list by fetching a configured URL and the server returned a non-2xx status. The error carries the HTTP status code, URL and elapsed time as context. `try_refresh` bails so the caller (`refresh`) can keep the previous list or retry later.

Source

Thrown at crates/store/src/backend/http/lookup.rs:110

        let time = Instant::now();
        let agent = BROWSER_USER_AGENTS.choose(&mut rand::rng()).unwrap();
        let response = self
            .client
            .get(&self.config.url)
            .timeout(self.config.timeout)
            .header(reqwest::header::USER_AGENT, *agent)
            .send()
            .await
            .map_err(|err| {
                trc::StoreEvent::HttpStoreError
                    .into_err()
                    .reason(err)
                    .ctx(trc::Key::Url, self.config.url.to_compact_string())
                    .details("Failed to build request")
            })?;

        if !response.status().is_success() {
            trc::bail!(
                trc::StoreEvent::HttpStoreError
                    .into_err()
                    .ctx(trc::Key::Code, response.status().as_u16())
                    .ctx(trc::Key::Url, self.config.url.to_compact_string())
                    .ctx(trc::Key::Elapsed, time.elapsed())
                    .details("Failed to fetch HTTP list")
            );
        }

        let bytes = response
            .bytes_with_limit(self.config.max_size)
            .await
            .map_err(|err| {
                trc::StoreEvent::HttpStoreError
                    .into_err()
                    .reason(err)
                    .ctx(trc::Key::Url, self.config.url.to_compact_string())
                    .ctx(trc::Key::Elapsed, time.elapsed())

View on GitHub (pinned to e962003857)

Solutions

  1. Check the URL in the error context returns 200 when fetched manually (curl -I).
  2. Fix the configured URL in config (http store url setting) if it is wrong or moved.
  3. Add required authentication/credentials expected by the list provider.
  4. Verify network/proxy reachability to the provider and check for rate limiting (status 429).
  5. Confirm the list service is up; rely on the cached list until it recovers.

Example fix

// before (config.toml)
url = "http://example.com/blocklist.txt"
// after
url = "https://lists.example.net/v2/blocklist.txt?apikey=YOUR_KEY"
Defensive patterns

Strategy: retry

Validate before calling

// before configuring, verify the URL serves the list
// curl -fsS -o /dev/null -w '%{http_code}' "$URL"  # expect 200

Try / catch

// retry transient failures, keep last-good list otherwise
match refresh().await {
    Err(err) if is_transient(&err) => schedule_retry(err, backoff),
    Err(err) => log::warn!("keeping cached list: {err}"),
    Ok(_) => {},
}

Prevention

When it happens

Trigger: Periodic or manual refresh issues a GET to `self.config.url` and `response.status().is_success()` is false (404, 403, 500, etc.).

Common situations: Misconfigured list URL; the list provider requires an API key that's missing/expired; provider outage or rate-limiting; self-hosted list server down.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/ca9a74ec865cd2a2. Report an issue: GitHub.