cloudflare/cloudflared · error

failed to fetch JWKS from %s

Error message

failed to fetch JWKS from %s

What it means

fetchJWKS retrieves the JSON Web Key Set from the auth domain's /cdn-cgi/access/certs endpoint using an http.Client with a 10s timeout and redirects disabled. This wrapped error means the HTTP transport itself failed — DNS failure, connection refused/timeout, TLS handshake error, or a client-side abort — before any HTTP status was received.

Source

Thrown at token/jwks.go:106

		return url.URL{}, fmt.Errorf("auth_domain %q does not end with %q", authDomain, accessDomainSuffix)
	}
	return url.URL{Scheme: httpsScheme, Host: hostname}, nil
}

// fetchJWKS fetches the JWKS from the auth domain's certs endpoint over HTTPS.
func fetchJWKS(authDomain url.URL) (*jose.JSONWebKeySet, error) {
	jwksURL := authDomain
	jwksURL.Path = accessCertPath

	client := &http.Client{
		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
			return http.ErrUseLastResponse
		},
		Timeout: time.Second * 10,
	}
	resp, err := client.Get(jwksURL.String()) // nolint: gosec
	if err != nil {
		return nil, errors.Wrapf(err, "failed to fetch JWKS from %s", jwksURL.String())
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("JWKS endpoint %s returned status %d", jwksURL.String(), resp.StatusCode)
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, maxJWKSResponseSize+1))
	if err != nil {
		return nil, errors.Wrap(err, "failed to read JWKS response body")
	}
	if len(body) > maxJWKSResponseSize {
		return nil, fmt.Errorf("JWKS response body exceeds %d bytes", maxJWKSResponseSize)
	}

	var keySet jose.JSONWebKeySet
	if err := json.Unmarshal(body, &keySet); err != nil {
		return nil, errors.Wrap(err, "failed to parse JWKS")

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Test reachability: `curl -v https://<team>.cloudflareaccess.com/cdn-cgi/access/certs` from the same host.
  2. Configure proxy environment variables (HTTPS_PROXY) if egress goes through a corporate proxy, or open egress to the auth domain on port 443.
  3. Fix DNS (check /etc/resolv.conf, try a public resolver) if the hostname does not resolve.
  4. If errors are transient, retry — getJWKSWithCache will reuse a fresh cached JWKS while the network is down (valid for 24h).
  5. Investigate TLS interception devices whose CA is not in the trust store if the error is a certificate error.

Example fix

// ensure proxy-aware client when egress requires it
client := &http.Client{
    Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
    CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse },
    Timeout: 10 * time.Second,
}
resp, err := client.Get(jwksURL.String())
Defensive patterns

Strategy: retry

Validate before calling

func canReachAuthDomain(authDomain string) error {
    conn, err := net.DialTimeout("tcp", authDomain+":443", 5*time.Second)
    if err != nil { return err }
    return conn.Close()
}

Try / catch

keySet, err := fetchJWKS(authDomain)
if err != nil && strings.Contains(err.Error(), "failed to fetch JWKS") {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        // retry with backoff; or serve from cached JWKS while offline
    }
    return fmt.Errorf("JWKS fetch failed (network/proxy/DNS): %w", err)
}

Prevention

When it happens

Trigger: fetchJWKS (via getJWKSWithCache or verifyMetadataWithRetry) when client.Get(jwksURL) returns a transport error: no network/route to the auth domain, DNS resolution failure, egress firewall or proxy blocking the request, the 10-second timeout elapsing, or an invalid TLS certificate on the endpoint.

Common situations: Cloudflared running in an air-gapped or firewalled environment; corporate proxy without HTTPS_PROXY configured; DNS not resolving the *.cloudflareaccess.com domain; IPv6-only or broken DNS in containers; intermittent network flaps during token validation.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/cd88ba81fe01d486. Report an issue: GitHub.