ory/hydra · error

expected status code 200 but got %d when requesting %s

Error message

expected status code 200 but got %d when requesting %s

What it means

Fetcher.GetKey fetches a JWKS document from f.remote over plain HTTP GET and requires HTTP 200. Any other status (404 wrong path, 401/403 auth required, 500 server error, 301/302 if redirects are not followed) aborts with this error instead of attempting to parse the body. It reports both the received status code and the requested URL.

Source

Thrown at oryx/jwksx/fetcher.go:53

// GetKey retrieves a JSON Web Key from the cache, fetches it from a remote if it is not yet cached or returns an error.
//
// DEPRECATED: Use FetcherNext instead.
func (f *Fetcher) GetKey(kid string) (*jose.JSONWebKey, error) {
	f.RLock()
	if k, ok := f.keys[kid]; ok {
		f.RUnlock()
		return &k, nil
	}
	f.RUnlock()

	res, err := f.c.Get(f.remote)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return nil, errors.Errorf("expected status code 200 but got %d when requesting %s", res.StatusCode, f.remote)
	}

	var set jose.JSONWebKeySet
	if err := json.NewDecoder(res.Body).Decode(&set); err != nil {
		return nil, errors.WithStack(err)
	}

	for _, k := range set.Keys {
		f.Lock()
		f.keys[k.KeyID] = k
		f.Unlock()
	}

	f.RLock()
	defer f.RUnlock()
	if k, ok := f.keys[kid]; ok {
		return &k, nil
	}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Verify the remote URL is the correct, complete JWKS endpoint (usually issuer + '/.well-known/jwks.json' or from the OpenID discovery 'jwks_uri').
  2. Check server logs / curl the URL to see the actual status and response body for the real cause (401 vs 404 vs 500).
  3. If the endpoint requires auth or is behind a proxy, configure an http.Client with the needed transport before constructing the fetcher.
  4. Implement retry with backoff for transient 5xx responses, and cache keys so a brief outage does not break verification.

Example fix

// before
f := jwksx.NewFetcher("https://issuer.example.com/jwks") // 404
// after
f := jwksx.NewFetcher("https://issuer.example.com/.well-known/jwks.json")
Defensive patterns

Strategy: retry

Validate before calling

resp, err := http.Head(jwksURL)
if err != nil || resp.StatusCode != 200 {
    return fmt.Errorf("JWKS URL %s not healthy (status %v)", jwksURL, statusOrNil(resp))
}

Try / catch

key, err := fetcher.GetKey(kid)
if err != nil && strings.Contains(err.Error(), "expected status code 200") {
    time.Sleep(backoff)
    key, err = fetcher.GetKey(kid) // retry transient 5xx
}

Prevention

When it happens

Trigger: Calling jwksx.NewFetcher(remote).GetKey(kid) where the HTTP GET of the JWKS URL (oryx/jwksx/fetcher.go:53) returns a non-200 status — first fetch or any cache-miss fetch.

Common situations: Misconfigured issuer/JWKS URL (404), the JWKS endpoint requiring auth (401/403), the identity provider being down (5xx), or pointing at an HTML page instead of a JWKS endpoint.

Understand the failure class

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/9147bba52d3a36b4. Report an issue: GitHub.