cloudflare/cloudflared · error
metadata JWT hostname %q does not match request host %q
Error message
metadata JWT hostname %q does not match request host %q
What it means
GetAppInfo rejects the metadata JWT when its Hostname claim does not case-insensitively match the hostname of the URL that was actually requested. This anti-spoofing check ensures the JWT describes the exact app you contacted; a mismatch means the token was issued for a different hostname (or an attacker is replaying a JWT across apps).
Source
Thrown at token/token.go:450
if err != nil {
return nil, err
}
// Parse auth_domain into the canonical hostname used for JWKS lookup.
authDomain, err := parseAuthDomain(unverified.AuthDomain)
if err != nil {
return nil, fmt.Errorf("metadata JWT auth_domain validation failed: %w", err)
}
// Verify the JWT signature against the JWKS (with disk cache + retry).
claims, err := verifyMetadataWithRetry(rawJWT, authDomain)
if err != nil {
return nil, fmt.Errorf("metadata JWT verification failed: %w", err)
}
// Verify the hostname in the JWT matches the URL we actually requested.
if !strings.EqualFold(claims.Hostname, reqURL.Hostname()) {
return nil, fmt.Errorf("metadata JWT hostname %q does not match request host %q", claims.Hostname, reqURL.Hostname())
}
if claims.Type != metadataMatchType {
return nil, fmt.Errorf("metadata JWT type %q is not match", claims.Type)
}
if claims.AUD == "" {
return nil, errors.New("metadata JWT aud is empty")
}
if err := validateMetadataIssuedAt(claims.IAT, time.Now()); err != nil {
return nil, err
}
appHostname := claims.AppHostname
if appHostname == "" {
// For retro-compatibility with CF access older releases, this will cause wildcard apps to store one local token
// per requested hostname, which is less optimized but also works.
appHostname = claims.Hostname
}
View on GitHub (pinned to 2253eeeb25)
Solutions
- Print both hostnames from the error and correct the reqURL to use the exact public hostname of the Access app.
- Check the Access application's configured domain in the Zero Trust dashboard matches the URL you request.
- Remove hosts-file/DNS overrides or reverse-proxy rules that rewrite the requested Host header.
- If using a wildcard app, request the concrete hostname and rely on AppHostname rather than reusing JWTs across hostnames.
- Ensure the JWT was freshly fetched for this URL (no cached/replayed metadata JWT from another app).
Example fix
// before: requesting via internal alias, JWT hostname mismatches
appURL, _ := url.Parse("https://app.internal/dashboard")
info, err := token.GetAppInfo(appURL) // hostname "app.internal" != "app.example.com"
// after: use the public Access hostname
appURL, _ := url.Parse("https://app.example.com/dashboard")
info, err := token.GetAppInfo(appURL) Defensive patterns
Strategy: validation
Validate before calling
publicHost := "app.example.com" // exact hostname configured in the Access application
if appURL.Hostname() != publicHost {
return fmt.Errorf("must request the Access app's public hostname %s, got %s", publicHost, appURL.Hostname())
} Type guard
func hostnameMatches(appURL *url.URL, accessAppHostname string) bool {
return appURL != nil && strings.EqualFold(appURL.Hostname(), accessAppHostname)
} Try / catch
info, err := token.GetAppInfo(appURL)
if err != nil {
if strings.Contains(err.Error(), "does not match request host") {
return fmt.Errorf("URL hostname does not match the Access application; use the exact public hostname configured in Zero Trust: %w", err)
}
return err
} Prevention
- Always request the exact public hostname configured on the Access application, not internal aliases or CNAMEs.
- Compare the error's two quoted hostnames to spot DNS/hosts overrides or proxy Host rewrites.
- Avoid caching or replaying metadata JWTs across different app hostnames.
- For wildcard apps, request concrete hostnames and expect AppHostname to carry the wildcard base.
When it happens
Trigger: Calling GetAppInfo with a reqURL whose hostname differs from the JWT's hostname claim — e.g. the HEAD request followed an implicit host rewrite, the URL was built with a different case/alias domain, a proxy answered on behalf of another vhost, or the JWT was captured from a different Access application.
Common situations: CNAME/vhost misconfiguration where one origin serves multiple Access apps, using an internal alias (e.g. app.internal) that maps to a different public hostname, stale token-replay from a sibling app, wildcard Access apps returning a different AppHostname, or hand-built URLs during testing.
Related errors
- metadata JWT auth_domain validation failed: %w
- metadata JWT verification failed: %w
- auth_domain %q does not end with %q
- invalid token
- metadata JWT aud is empty
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/ad8acbc939ff5ce2.
Report an issue: GitHub.