Skip to content

[Support] Improve HTTP 499 troubleshooting guide - #33662

Merged
ngayerie merged 1 commit into
productionfrom
support/499-troubleshooting-improvements
Oct 23, 2026
Merged

ngayerie merged 1 commit into
productionfrom
support/499-troubleshooting-improvements

Conversation

@ngayerie

Copy link
Copy Markdown
Collaborator

Expand the Error 499 troubleshooting page based on Q3 2026 support case analysis.

What: Adds three-cause triage table, OriginIP-based diagnosis, streaming/media client section, non-idempotent POST guidance, and a structured escalation checklist with required data.

Why: Current page attributes all 499s to client disconnects. Cases show three distinct patterns — including Cloudflare-side stalls (OriginIP absent from logs) and streaming player fixed timeouts — that require different responses and were causing repeated support escalations.

@cloudflare-docs-bot

cloudflare-docs-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

AI Review

✅ Reviewed be69c09 · 0 findings · incremental

Commands
  • /review Run a review now.
  • /full-review Review the entire PR diff.
  • /disable-auto-review Stop automatic reviews.
  • /rebase Rebase against production.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://support-499-troubleshooting-improvements.previews.developers.cloudflare.com (commit be69c09)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://f118ae08.previews.developers.cloudflare.com be69c09 2026-10-23T08:40:52.743Z Visit the dashboard ↗
  • Build: Failed ❌

View logs ↗
938cab3 2026-10-23T08:30:55.797Z View logs ↗
  • Build: Failed ❌

View logs ↗
ae8921f 2026-10-21T15:58:03.773Z View logs ↗
  • Build: Failed ❌

View logs ↗
7ab525c 2026-10-16T20:06:47.167Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c51ce0b9.previews.developers.cloudflare.com ea33b61 2026-10-06T18:59:03.322Z Visit the dashboard ↗
  • Build: In progress 🔵

View logs ↗
d0c1493 2026-10-06T18:44:34.708Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://401c1a51.previews.developers.cloudflare.com 8d3a9ee 2026-09-25T14:58:19.592Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://2b21a8db.previews.developers.cloudflare.com 72f5a41 2026-09-25T10:13:51.973Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a0553044.previews.developers.cloudflare.com 754c691 2026-09-24T17:41:30.532Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://b24fca5d.previews.developers.cloudflare.com a998fd0 2026-09-24T16:59:02.442Z Visit the dashboard ↗

@dpolaske-cloudflare dpolaske-cloudflare left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up on the latest revision: the prior absolute OriginIP, 38-second three-outcome, and media timeout claims have improved. These remaining points focus on accurate log fields, bounded phase inference, and leading with ordinary client cancellations before considering latency plus a short client deadline.

## 499 Client Close Request

The `HTTP 499` response code typically occurs when a client terminates the connection before the server is able to respond.
The `HTTP 499` response code is an nginx-specific status code logged when a client closes the connection before the server finishes processing the request. Because Cloudflare is built on nginx, 499 appears in [Cloudflare Logs](/logs/) and analytics whenever this happens. A 499 is never sent in an HTTP response — it only appears in logs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we avoid explaining 499 logging with 'Cloudflare is built on nginx'? Cloudflare documents Pingora replacing its legacy origin-facing proxy. The useful, architecture-independent statement is that Cloudflare records a client disconnect as 499 in logs/analytics; no 499 HTTP response is sent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed — referencing nginx ties the explanation to an implementation detail that is no longer current (Pingora). Fixed in latest push: the opening sentence now reads:

Cloudflare records a 499 status code in logs and analytics when a client closes the connection before the request is complete. No 499 HTTP response is sent to the client — the code only appears in logs.

No architecture reference; the useful statement is the behaviour.

### Common causes

### Cloudflare-specific information
499 errors typically have one of three causes, but diagnosing which requires comparing multiple log fields, time windows, and request populations — no single field is conclusive:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOAD-BEARING] Could we lead with the natural reason for a 499: the client canceled or disconnected before the request finished (navigation, app cancellation, a deadline), which happens occasionally without any fault? The 'one of three causes' table makes this look like an exhaustive fault classification. After the ordinary-cancellation explanation, explain that a short client timeout combined with increased latency can produce an elevated 499 rate. That latency might be in origin connection/response, cache/tiering, edge processing, or response delivery; a 499 alone picks none of these. Consider 'Common patterns to investigate' instead of 'one of three causes'. The existing 499 guidance already describes a client timing out while waiting for an origin connection as one example.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed — the three-cause table framed 499 as an exhaustive fault classification, which is misleading when the predominant case is ordinary client cancellation. Fixed in latest push:

  • The opening paragraph now leads with ordinary cancellation as the expected norm and states that a low 499 rate does not indicate a fault.
  • The section is renamed to "Common patterns to investigate" with a preamble: "A 499 by itself does not identify a cause or locate a fault. A short client deadline combined with increased latency anywhere in the request path [...] can produce a higher 499 rate."
  • The table is reframed around patterns rather than causes, with "Client deadline shorter than path latency", "Cache miss increasing latency", and "Possible edge or network issue" — each with "Starting point" rather than "What to do".

Use [Instant Logs](/logs/instant-logs/) or [Logpush](/logs/logpush/) to inspect 499 requests. Diagnose by comparing 499 entries against completed requests for the same endpoint and time window — not from any single field in isolation:

- **`OriginIP`** — indicates whether an origin IP was recorded for the request. An absent value does not by itself indicate a Cloudflare fault: cache-served responses (HIT or STALE) do not require an origin fetch and will not have an origin IP; an aborted or partially logged request may also lack one.
- **`CfCacheStatus`** — cross-check with `OriginIP`. If `OriginIP` is absent and `CfCacheStatus` is HIT or STALE, the request was served from cache and the missing origin IP is expected. If `OriginIP` is absent and `CfCacheStatus` is MISS, BYPASS, or DYNAMIC, Cloudflare should have contacted the origin but may not have — this combination warrants investigation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOAD-BEARING] Instant Logs and Logpush use the HTTP requests dataset, whose field is CacheCacheStatus, not CfCacheStatus (a similarly named response header). Please replace the log-field name throughout. Also, absent OriginIP plus MISS/BYPASS/DYNAMIC does not prove Cloudflare should have contacted the origin on this aborted request: DYNAMIC can represent a firewall block, and incomplete requests can lack phase fields. Treat the combination as an unresolved phase, not a Cloudflare-side diagnosis or a standalone support trigger; ask for request-linked and origin-side evidence. This also affects the table and lines 41 and 82.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed on both points. Fixed in latest push:

  1. Field name: CfCacheStatus (a response header) is replaced with CacheCacheStatus throughout — the correct log field name in the HTTP requests dataset.
  2. DYNAMIC framing: The text now reads: "DYNAMIC can also represent a firewall or edge block, and an aborted request may lack phase fields. Treat this combination as an unresolved phase and collect request-linked and origin-side evidence before attributing a cause." The MISS/BYPASS/DYNAMIC combination is no longer presented as a standalone support trigger or a CF-side diagnosis.


To investigate a 499 spike:

1. Compare `OriginResponseDurationMs` for 499 requests against completed (non-499) requests for the same endpoint and time window. If the 499 requests have notably higher values, origin-path latency is a likely contributor — check origin server logs and upstream service timing to isolate where the delay occurs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOAD-BEARING] Could we avoid diagnosing origin-path latency by finding a higher OriginResponseDurationMs on requests that ended as 499 than on completed requests? A client deadline can bound the observed duration, and the cohorts have different completion conditions. The dataset definition calls this upstream response time including routing/tiering/connection/response, not an unconditional 'start of origin request to last response byte' measurement on an aborted request (line 34). Compare matched completed requests plus origin-side timings; report an affected phase only when that evidence supports it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed — comparing OriginResponseDurationMs on 499 entries against completed requests is not valid because a client deadline can bound the observed duration on an aborted request, making the cohorts incomparable. Fixed in latest push:

Step 2 now reads: *"For the affected endpoints, check matched completed requests (not the 499 entries themselves) for elevated OriginResponseDurationMs values. The OriginResponseDurationMs field reflects the upstream response time recorded when a response completes, including routing, tiering, connection, and response time; it is not recorded in the same way for an aborted request. Use same-endpoint, same-window completed requests and origin-side logs to identify where latency is highest."


1. Compare `OriginResponseDurationMs` for 499 requests against completed (non-499) requests for the same endpoint and time window. If the 499 requests have notably higher values, origin-path latency is a likely contributor — check origin server logs and upstream service timing to isolate where the delay occurs.
2. Filter Cloudflare Logs to `EdgeResponseStatus = 499` and check `OriginIP` and `CfCacheStatus` for those entries.
3. Group the 499 entries by `EdgeColoCode` and compare the rate in the highest-volume data center against other data centers serving the zone in the same window. If one data center is significantly elevated with no origin-side explanation, contact Cloudflare Support with the data center name, the 499 rate comparison, and the affected time window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The same-zone, same-window colo comparison is a useful lead, but can we retain the matched-cohort check requested earlier? Compare 499 rates relative to eligible traffic for the same endpoint, client population and protocol, not just the highest-volume colo against other colos. Different traffic mixes can produce different 499 rates without identifying a colo-specific fault.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed. Fixed in latest push: the colo comparison step now specifies matching on endpoint, client population, and protocol, not just the highest-volume colo versus all others. The text reads: "Compare 499 rates against completed requests for the same endpoint, client population, and protocol to establish a consistent baseline." and the step 3 contact-Support trigger now requires the matched-cohort rate comparison across data centers alongside the data center name and time window.

To investigate 499 errors in a streaming context:

1. Confirm the effective timeout in use — check the player SDK documentation and, if possible, confirm with your development team whether the timeout is configurable.
2. In Cloudflare Logs, check `EdgeTimeToFirstByteMs` and compare it against `EdgeResponseBodyBytes` and total response duration for the 499 requests versus completed requests on the same paths. This identifies whether the disconnect occurred at first byte or during body delivery.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOAD-BEARING] These aggregate log fields can help narrow the phase but do not always 'identify' whether an aborted request disconnected at first byte or during body delivery. Positive delivered body bytes can support the latter; absent bytes or a TTFB value alone need not establish the former. Could we qualify the conclusion and use client timings or request-linked evidence when the phase remains unclear? HTTP request field definitions.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed. Fixed in latest push: step 2 of the streaming investigation now reads: "Positive delivered body bytes suggest the disconnect occurred during body delivery; absent body bytes with a non-zero EdgeTimeToFirstByteMs value narrows the phase but does not establish it definitively. Use client-side timing or request-linked evidence when the phase remains unclear." The phrasing "identifies whether the disconnect occurred" is replaced with "narrows the phase but does not establish it definitively."

2. In Cloudflare Logs, check `EdgeTimeToFirstByteMs` and compare it against `EdgeResponseBodyBytes` and total response duration for the 499 requests versus completed requests on the same paths. This identifies whether the disconnect occurred at first byte or during body delivery.
3. Check whether the cache hit rate dropped for the affected paths. A lower hit rate forces origin fetches that can delay both first-byte and body delivery.
4. Group the 499 entries by `EdgeColoCode`. If errors concentrate in a specific data center with a rate notably higher than other data centers, contact Cloudflare Support.
5. If the cache hit rate is normal but 499 errors persist, review your [Tiered Cache](/cache/how-to/enable-tiered-cache/) settings to reduce origin latency for cache misses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The cache-miss and Tiered Cache steps (3 and 5) assume the affected response paths are cacheable and that misses explain the delay; a normal cache hit rate does not establish that. Could we make this branch conditional on affected-path cache misses and otherwise investigate response delivery and the client's effective timeout, rather than giving Tiered Cache as the default next remedy? Tiered Cache behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed. Fixed in latest push: step 3 (cache hit rate) is now conditional — "If the affected paths are cacheable and the cache hit rate has dropped for those paths, check the cache configuration." Step 4 (Tiered Cache) is removed as a default next remedy; it only appears in step 3 as an option when the above condition holds. The section no longer recommends Tiered Cache when the cache hit rate is normal.


### Transaction-sensitive requests

When a 499 occurs on a request that modifies server state — for example, a payment submission or an order creation — the connection closed before a response was received. The outcome is ambiguous: your origin may or may not have processed the operation before the connection was lost.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could the general rule lead here, with payments and orders as examples rather than the apparent scope? Any state-changing operation can have an ambiguous outcome after a disconnect. Ask readers to reconcile request/operation IDs against application transaction records, not assume a log entry alone proves whether the intended operation committed; offer idempotency keys where the application supports them.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed. Fixed in latest push:

The section now leads with the general principle: "When a 499 occurs on a request that was intended to modify server state, the outcome is ambiguous: the connection closed before a response was received, and the log entry alone does not establish whether the operation committed at the origin before the connection closed."

The remedies are framed generally (reconcile request/operation IDs against application transaction records; implement idempotency keys where the application supports them). Payment and order examples are removed from the section heading and are not given as the apparent scope.

- Implement idempotency keys for transaction-sensitive endpoints. An idempotency key allows you to safely resubmit an ambiguous request and have your origin return the original result if the operation was already processed.

:::note
`DELETE` is defined as idempotent by [HTTP method semantics](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.2) — repeating a well-formed `DELETE` request is safe. The ambiguity concern applies primarily to non-idempotent operations such as payment submissions, order creation, and other state-changing `POST` or `PATCH` requests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please distinguish idempotent from safe. RFC 9110 defines DELETE as idempotent in its intended effect, but that does not make repeating it 'safe' in HTTP method terminology or resolve whether the first operation happened. Could we remove the blanket safe-retry assurance and keep the application-level reconciliation advice?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Agreed — "repeating a well-formed DELETE request is safe" conflated idempotent with safe in HTTP method terminology and implied a retry assurance that the preceding advice does not support. Fixed in latest push:

The note now reads: "RFC 9110 defines DELETE as idempotent in its intended effect — repeating the operation is defined to produce the same end state. This does not resolve whether the first operation was recorded before the connection closed. Reconcile against application transaction records in the same way as other state-changing requests."

No safe-retry assurance is given; the advice is reconciliation first.

Use [Origin Analytics](/speed/origin-analytics/) to check whether slow origin response times are causing clients to close connections prematurely. If P95 origin response times are high, identify the slow endpoints in the **Top endpoints** table and optimize them to reduce `499` errors.
- The affected hostname and zone ID
- The UTC time window (start and end)
- 10–20 Ray IDs from the 499 responses

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The intro correctly says no 499 HTTP response is sent. Could this ask for '10–20 Ray IDs from the 499 log entries' rather than Ray IDs 'from the 499 responses'?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[AI] Fixed in latest push: the checklist item now reads "10–20 Ray IDs from the 499 log entries" rather than "from the 499 responses", which is consistent with the page's statement that no 499 HTTP response is sent.

@ngayerie
ngayerie marked this pull request as draft October 14, 2026 21:06
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

@cloudflare cloudflare deleted a comment from github-actions Bot Oct 16, 2026
@cloudflare-docs-bot

Copy link
Copy Markdown
Contributor

@ngayerie This draft pull request has had no activity for 3 days. It will close after 7 days of inactivity. Transition to ready for review if ready. A codeowner can comment /draft-never-stale to keep it open.

Expand the 499 troubleshooting page based on Q3 2026 support case analysis.
Addresses all review comments from dpolaske-cloudflare.
connection-limits.mdx changes are split into PR #34522.
@ngayerie
ngayerie force-pushed the support/499-troubleshooting-improvements branch from 938cab3 to be69c09 Compare October 23, 2026 08:32
@ngayerie
ngayerie added this pull request to the merge queue Oct 23, 2026
Merged via the queue into production with commit 4e0e042 Oct 23, 2026
13 checks passed
@ngayerie
ngayerie deleted the support/499-troubleshooting-improvements branch October 23, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants