cloudflare/cloudflared · info

forbidden

Error message

forbidden

What it means

The metrics server's router explicitly blocks /debug/pprof/cmdline and responds 'forbidden' with HTTP 403. This is intentional security behavior: the cmdline pprof endpoint would expose the full command line (os.Args), which can contain secret tunnel tokens or credentials. Hitting this error means you requested a deliberately disabled debug endpoint.

Source

Thrown at metrics/metrics.go:76

	QuickTunnelHostname string
	Orchestrator        orchestrator

	ShutdownTimeout time.Duration
}

type orchestrator interface {
	GetVersionedConfigJSON() ([]byte, error)
}

func newMetricsHandler(
	config Config,
	log *zerolog.Logger,
) *http.ServeMux {
	router := http.NewServeMux()
	// Block /debug/pprof/cmdline to prevent leaking secret command-line arguments
	// (e.g. tunnel tokens) that are exposed via os.Args.
	router.HandleFunc("/debug/pprof/cmdline", func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "forbidden", http.StatusForbidden)
	})
	router.Handle("/debug/", http.DefaultServeMux)
	router.Handle("/metrics", promhttp.Handler())
	router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprintf(w, "OK\n")
	})
	if config.ReadyServer != nil {
		router.Handle("/ready", config.ReadyServer)
	}
	router.HandleFunc("/quicktunnel", func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, config.QuickTunnelHostname)
	})
	if config.Orchestrator != nil {
		router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
			json, err := config.Orchestrator.GetVersionedConfigJSON()
			if err != nil {
				w.WriteHeader(500)
				_, _ = fmt.Fprintf(w, "ERR: %v", err)

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Do not rely on /debug/pprof/cmdline; use the process command line via ps/proc instead: ps -o args= -p $(pidof cloudflared)
  2. Profile via the other pprof endpoints (/debug/pprof/profile, /debug/pprof/heap) which remain served under /debug/
  3. If you need the flags, inspect your own deployment configuration or systemd unit rather than the endpoint
  4. This is by design to prevent token leakage — do not attempt to bypass it; rotate any tokens you consider exposed

Example fix

// before: fetching command line via blocked endpoint
curl http://localhost:20241/debug/pprof/cmdline
// after: read it from the process instead
ps -o args= -p "$(pidof cloudflared)"
Defensive patterns

Strategy: fallback

Validate before calling

if strings.HasSuffix(pprofPath, "/debug/pprof/cmdline") {
	// skip: intentionally blocked by cloudflared metrics router (403)
}

Try / catch

resp, err := http.Get(metricsURL + "/debug/pprof/profile?seconds=10")
if err == nil && resp.StatusCode == http.StatusForbidden {
	// cmdline/profile blocked: gather diagnostics from OS tooling instead
}

Prevention

When it happens

Trigger: An HTTP GET/POST to http://<metrics-host>:<metrics-port>/debug/pprof/cmdline on the cloudflared metrics listener returns 403 with body 'forbidden'.

Common situations: Developer profiling cloudflared with standard pprof tooling expects cmdline to work; security scanners flagging the endpoint; automation scraping all pprof endpoints; users unfamiliar with why this one endpoint is blocked while /debug/ itself is served.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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