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

  1. Print both hostnames from the error and correct the reqURL to use the exact public hostname of the Access app.
  2. Check the Access application's configured domain in the Zero Trust dashboard matches the URL you request.
  3. Remove hosts-file/DNS overrides or reverse-proxy rules that rewrite the requested Host header.
  4. If using a wildcard app, request the concrete hostname and rely on AppHostname rather than reusing JWTs across hostnames.
  5. 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

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


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