ginuerzh/gost · error
returned status code %d
Error message
returned status code %d
What it means
The DoH server answered, but with a non-200 HTTP status (resolver.go:913). dohExchanger.Exchange only accepts http.StatusOK, since a DNS-over-HTTPS response must be 200 with an application/dns-message body per RFC 8484; any other code (4xx/5xx) means the query was not successfully processed.
Source
Thrown at resolver.go:913
}
// req.Header.Add("Content-Type", "application/dns-udpwireformat")
req.Header.Add("Content-Type", "application/dns-message")
req.Host = ex.endpoint.Hostname()
client := ex.client
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to perform an HTTPS request: %s", err)
}
// Check response status code
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("returned status code %d", resp.StatusCode)
}
// Read wireformat response from the body
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read the response body: %s", err)
}
return buf, nil
}
View on GitHub (pinned to a33fdbf4c9)
Solutions
- Log/inspect resp.StatusCode (wrap it into the error at the call site) and match it against the DoH provider's documented codes
- Verify the endpoint URL includes the correct path (usually https://provider/dns-query)
- If 429: add backoff/retry with jitter or reduce query rate
- If 401/403: supply required auth (token, mTLS via tlsConfig) for the resolver
- If 5xx: retry later or switch to a backup DoH provider
Example fix
// before
buf, err := exchanger.Exchange(ctx, query)
// err: returned status code 404 (wrong path in endpoint URL)
// after
endpoint, _ := url.Parse("https://dns.example.com/dns-query") // correct RFC 8484 path
exchanger := NewDoHExchanger(endpoint, nil)
buf, err := exchanger.Exchange(ctx, query) Defensive patterns
Strategy: retry
Validate before calling
u, err := url.Parse(dohURL)
if err != nil || u.Path == "" || u.Path == "/" {
return fmt.Errorf("DoH endpoint likely missing /dns-query path: %q", dohURL)
} Type guard
func isNon200Status(err error) bool {
return err != nil && strings.Contains(err.Error(), "returned status code ")
}
func extractStatusCode(err error) (int, bool) {
if !isNon200Status(err) { return 0, false }
var code int
_, scanErr := fmt.Sscanf(err.Error(), "returned status code %d", &code)
return code, scanErr == nil
} Try / catch
buf, err := exchanger.Exchange(ctx, query)
if err != nil {
if code, ok := extractStatusCode(err); ok {
switch {
case code == http.StatusTooManyRequests:
// back off and retry later
case code == http.StatusNotFound:
// fix endpoint path
case code >= 500:
// retry with backoff or fail over to another resolver
}
}
return err
} Prevention
- Use the provider's documented RFC 8484 path (usually /dns-query)
- Parse the endpoint with url.Parse and validate scheme+path before constructing the exchanger
- Add retry with exponential backoff for 429/5xx responses
- Supply auth/mTLS when the resolver requires it
- Monitor provider status pages and configure a backup DoH resolver
When it happens
Trigger: Exchange() on a dohExchanger where client.Do succeeded but the server returned 400 (malformed DNS query), 404/410 (wrong endpoint path), 429 (rate limiting), 401/403 (auth required), 500/502/503 (server error), or a proxy returning an error page.
Common situations: DoH endpoint URL pointing at the wrong path (e.g. missing /dns-query); server rate-limiting high query volume; reverse proxy/auth gateway in front of the resolver rejecting requests; DoH provider outage returning 5xx; sending queries the resolver refuses to process.
Related errors
- failed to perform an HTTPS request: %s
- failed to read the response body: %s
- failed to create an HTTPS request: %s
- resolver: domain %s does not exists
- connection queue is full
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/80084512384a4fb6.
Report an issue: GitHub.