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

  1. Log/inspect resp.StatusCode (wrap it into the error at the call site) and match it against the DoH provider's documented codes
  2. Verify the endpoint URL includes the correct path (usually https://provider/dns-query)
  3. If 429: add backoff/retry with jitter or reduce query rate
  4. If 401/403: supply required auth (token, mTLS via tlsConfig) for the resolver
  5. 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

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


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/80084512384a4fb6. Report an issue: GitHub.