Skip to content

Make Access login/token HTTP timeouts configurable - #1721

Open
alexmerm wants to merge 3 commits into
cloudflare:masterfrom
alexmerm:claude/cloudflared-access-timeouts-1rniwn
Open

Make Access login/token HTTP timeouts configurable#1721
alexmerm wants to merge 3 commits into
cloudflare:masterfrom
alexmerm:claude/cloudflared-access-timeouts-1rniwn

Conversation

@alexmerm

@alexmerm alexmerm commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Two independent HTTP timeouts on the access login / access token path are hardcoded with no flag or env var to raise them, and fail hard on slow or high-latency connections:

  • isTokenValid (cmd/cloudflared/access/cmd.go) uses a 5s timeout when checking a cached token against the origin.
  • fetchMetadataJWT (token/token.go) uses a 7s timeout when fetching the Access application's metadata JWT from the Cloudflare edge.

This is a real, reported problem, not a hypothetical one. #1637 hits fetchMetadataJWT's timeout with the exact error:

failed to get app info: Head "..." context deadline exceeded (Client.Timeout exceeded while awaiting headers)

#740 hits isTokenValid's timeout, reporting that access login succeeds but still prints a timeout error (Could not verify token). Neither issue has a maintainer response or a linked fix.

I checked the git history for both hardcoded values before touching them:

  • The 5s timeout was introduced in 1d5cc45a ("AUTH-2055: Verifies token at edge on access login", Sep 2019).
  • The 7s timeout was introduced in fcc393e2 ("AUTH-3221: Saves org token to disk and uses it to refresh the app token", Nov 2020).

Both commit messages are one-line internal Jira references with no rationale, there's no code comment near either timeout explaining the duration, and neither has been touched since introduction. This isn't loosening a deliberately-tuned constraint — there doesn't appear to have been a documented one.

What changed

Added one new flag/env var pair, on the access command:

  • --access-timeout (duration)
  • TUNNEL_ACCESS_TIMEOUT (env var)
  • Default: 7s — the larger of the two current hardcoded values, so nobody's effective timeout gets shorter by default.

This single knob governs both call sites. The 5s vs. 7s split doesn't look like two deliberately different budgets for two different kinds of request — it looks like two independently-chosen numbers that happened to land close together (see git history above). Adding a second flag would just be reintroducing that same unexplained asymmetry with more surface area. One operator-facing timeout for "Access login/token HTTP requests" is the right level of granularity here.

Threading:

  • token.fetchMetadataJWT and token.GetAppInfo now take a timeout time.Duration parameter instead of hardcoding 7 * time.Second. All 4 callers in cmd/cloudflared/access/cmd.go (login, curl, token, ssh-gen) now pass c.Duration(accessTimeoutFlag).
  • carrier.StartOptions gained a Timeout time.Duration field. verifyTokenAtEdge/isTokenValid (used by login and curl) now read it instead of hardcoding 5 * time.Second, falling back to token.DefaultAccessTimeout if unset. The access tcp/ssh command sets it on StartOptions too, so carrier/websocket.go's token.GetAppInfo call (reached via the websocket/forwarder path) also gets a configured timeout, again falling back to token.DefaultAccessTimeout (7s) when nothing is configured — this keeps the config-file-driven forwarder path (carrier.StartForwarder, which has no CLI flags at all) behaviorally unchanged.
  • Added token.DefaultAccessTimeout = 7 * time.Second as the single source of truth for the default/fallback value.

No behavior change for anyone who doesn't pass the new flag/env var — the default preserves today's ceiling (7s) exactly.

Test plan

  • go build ./...
  • go vet ./...
  • gofmt -l clean on all touched files
  • go test ./token/... ./cmd/cloudflared/access/...
  • go test ./carrier/... (touched carrier.StartOptions)

New tests added:

  • token.TestFetchMetadataJWT_HonorsConfiguredTimeout — server sleeps 500ms, request configured with a 100ms timeout, asserts the call fails in well under the old hardcoded 7s (i.e. the configured timeout actually fires).
  • token.TestFetchMetadataJWT_DefaultTimeoutPreservesCurrentBehavior — asserts DefaultAccessTimeout == 7s and that a fast response still succeeds under the default.
  • cmd/cloudflared/access.TestAccessTimeoutFlag_DefaultPreservesCurrentBehavior / TestAccessTimeoutFlag_EnvVarOverridesDefault / TestAccessTimeoutFlag_FlagOverridesEnvVar — precedence tests against the actual flag registered on the access command (flag > env var > default).

Also updated existing token package tests (TestGetAppInfo_*, TestFetchMetadataJWT_ReturnsAppInfoErrorOnError) to pass DefaultAccessTimeout for the new required parameter; no behavioral change to those tests.

Scope note: this PR is config-threading only — no unrelated refactors in either file. CLI help text (--access-timeout usage string) is the only docs change; cloudflared's docs site lives in a separate cloudflare-docs repo and is out of scope here.


Generated by Claude Code

Note on default-timeout behavior change

Correction to the "No behavior change" claim above: for isTokenValid specifically, the default ceiling actually rises from 5s → 7s for anyone who doesn't pass --access-timeout/TUNNEL_ACCESS_TIMEOUT, since both call sites now share token.DefaultAccessTimeout (7s) as their fallback rather than each hardcoding its own value. This is intentional — see the "What changed" rationale above (the 5s/7s split looks like two independently-chosen numbers, not a deliberately tuned budget) — but it is a real behavior change worth flagging explicitly for review, not just an implementation detail.

Context

Small footnote: this PR happened because I was stuck on a plane on the tarmac for about 5 hours with terrible in-flight wifi, and slow/flaky connections timing out on access login was very much a lived problem at the time.

fetchMetadataJWT (7s) and isTokenValid (5s) hardcode independent
http.Client timeouts on the access login/token path, with no flag or
env var to raise them. Both were introduced without documented
rationale (1d5cc45 / AUTH-2055, fcc393e / AUTH-3221) and have gone
untouched since, while cloudflare#1637 and cloudflare#740 report
exactly this failure mode on slow connections.

Add a single --access-timeout flag (TUNNEL_ACCESS_TIMEOUT env var,
default 7s) that governs both call sites. The two existing values
look like independent arbitrary choices rather than distinct budgets
for distinct request types, so one operator-facing knob replaces both
instead of adding a second flag. Default matches today's larger value,
so behavior is unchanged unless an operator opts in.
Copilot AI lite review requested due to automatic review settings August 15, 2026 05:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the HTTP timeouts used by cloudflared access login / cloudflared access token configurable via a new --access-timeout flag (and TUNNEL_ACCESS_TIMEOUT env var), replacing previously hardcoded 5s and 7s timeouts in the Access token verification and app-metadata lookup flows.

Changes:

  • Introduces --access-timeout / TUNNEL_ACCESS_TIMEOUT (defaulting to token.DefaultAccessTimeout = 7s) on the access command and threads it through relevant call paths.
  • Updates token.GetAppInfo / fetchMetadataJWT to accept a timeout parameter rather than hardcoding the HTTP client timeout.
  • Extends carrier.StartOptions with a Timeout field and uses it in both token verification and websocket-based app info discovery; adds tests for flag/env precedence and timeout honoring.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
token/token.go Adds DefaultAccessTimeout and threads a configurable HTTP timeout into metadata JWT fetch + app info discovery.
token/token_test.go Updates existing tests for new timeout parameter and adds coverage that the configured timeout is honored.
cmd/cloudflared/access/cmd.go Registers --access-timeout flag/env var and passes it into token.GetAppInfo and token verification options.
cmd/cloudflared/access/cmd_test.go Adds tests for default/env/flag precedence on the new timeout flag.
cmd/cloudflared/access/carrier.go Passes the configured timeout into carrier.StartOptions for access ssh path.
carrier/websocket.go Uses StartOptions.Timeout (with fallback) when calling token.GetAppInfo for Access-protected origins.
carrier/carrier.go Adds Timeout time.Duration to carrier.StartOptions with documentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +20 to +28
cmds := Commands()
require.Len(t, cmds, 1)
for _, f := range cmds[0].Flags {
if df, ok := f.(*cli.DurationFlag); ok && df.Name == accessTimeoutFlag {
return df
}
}
t.Fatal("access-timeout flag not registered on the access command")
return nil
Comment thread token/token.go
Comment on lines +371 to +373
func GetAppInfo(reqURL *url.URL, timeout time.Duration) (*AppInfo, error) {
// Fetch the metadata JWT from the edge (no redirects followed).
rawJWT, err := fetchMetadataJWT(reqURL.String())
rawJWT, err := fetchMetadataJWT(reqURL.String(), timeout)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 75213fc: fetchMetadataJWT now clamps timeout <= 0 to token.DefaultAccessTimeout at the top of the function, before constructing the http.Client. Since GetAppInfo passes timeout straight through to fetchMetadataJWT, this fixes all 4 call sites in cmd.go in one place. Added a regression test (TestFetchMetadataJWT_ZeroTimeoutFallsBackToDefault) that verifies a zero timeout falls back to the 7s default instead of disabling the timeout.

Comment thread carrier/carrier.go
Comment on lines +37 to +40
// Timeout is the HTTP timeout used for Access login/token requests, such as
// fetching app metadata from the edge and verifying a cached token against
// the origin. If zero, callers fall back to token.DefaultAccessTimeout.
Timeout time.Duration
Alex Kaish and others added 2 commits August 15, 2026 02:18
fetchMetadataJWT passed the configured timeout straight to
http.Client.Timeout, where 0 disables timeouts entirely in net/http.
A user setting --access-timeout=0 (or an invalid negative value) would
hang indefinitely on login/curl/token paths, unlike isTokenValid and
createWebsocketStream which already fall back to
token.DefaultAccessTimeout for non-positive values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…logic

exchangeOrgToken still hardcoded a 7s HTTP timeout, ignoring the
--access-timeout flag for the org-token-exchange step of Access login.
Thread the resolved timeout through FetchToken/FetchTokenWithRedirect
so it applies here too.

Also extract the repeated `timeout <= 0 -> DefaultAccessTimeout` clamp
(previously duplicated in token.go, cmd.go, and websocket.go) into a
single token.ResolveAccessTimeout helper, since the duplication is how
exchangeOrgToken was missed in the first place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants