Production Debugging: A Practical Framework for Web Request Issues

13 min read

The earlier parts of this series covered DNS, TLS, HTTP, the server, and the browser's rendering pipeline. This final part is deliberately less about definitions and more about order: what to inspect first, which evidence separates similar symptoms, and how to avoid changing three layers at once.

This is Part 7, and the closing piece, of the How the Web Works series.


Quick Answer

Most production web issues start in one layer, even if the symptoms show up elsewhere. The fastest path to a fix is identifying the layer first instead of guessing from symptoms:

Is it slow?                →  could be DNS, TLS, server, or rendering; check the waterfall first
Is it failing outright?     →  check the status code family (4xx vs 5xx) to know who's at fault
Is it inconsistent?         →  suspect caching, load balancing, or a specific unhealthy instance
Is it visual/interactive?   →  suspect rendering, hydration, or layout shift

The useful habit: open the Network tab before forming a theory, not after. Many "mystery" bugs become clearer once you can see the actual request, response, and timing.


A Framework for Debugging Any Web Issue

Most issues in this series can be triaged with the same three questions, in order:

1. Which layer owns this?        (DNS → TLS → HTTP → server → rendering)
2. Is it happening for everyone, or just some users/networks/browsers?
3. Is it consistent, or intermittent?

Answering these three narrows the search space before you touch a tool. "Slow for everyone, consistently" points somewhere different than "slow for some users, intermittently." The first suggests a server or rendering bottleneck; the second suggests caching, load balancing, or a specific network path.


Debugging Slow Page Loads

Start with the browser's Network waterfall (DevTools → Network), which visually breaks a page load into its actual phases:

DNS lookup  →  TLS handshake  →  Time to First Byte  →  Content download  →  Rendering

Each phase maps directly to a part of this series:

Waterfall phaseSlow here means...Relevant part
DNS lookupSlow resolver, or missing/expired local cachePart 3
Connection + TLSSlow handshake, missing OCSP stapling, unnecessarily long cert chainPart 4
Time to First Byte (TTFB)Slow server processing, database bottleneck, cold startPart 5
Content downloadLarge uncompressed payloads, no CDN, poor cachingPart 2
Rendering / Loading eventsRender-blocking resources, heavy JS, layout thrashingPart 6

A single number like "the page takes 4 seconds" is not enough for debugging. The waterfall tells you which phase those 4 seconds are spent in, and that phase points you at the right part of the stack.


Debugging Failed Requests (4xx/5xx)

The first move is always the same: read the actual status code family before doing anything else, because it tells you who's responsible before you've looked at a single log line (see Part 2 for the full breakdown).

4xx  →  something about the request itself is wrong (client-side, or client-supplied data)
5xx  →  the server failed to handle a request that was otherwise valid

For 5xx specifically, the exact code narrows it further:

500  →  the origin server itself threw an unhandled error; check application logs
502  →  a proxy/gateway got an invalid response from something behind it
503  →  the server is intentionally not serving right now (overload, maintenance, rate limit)
504  →  a proxy/gateway never got a response in time from something behind it

502/504 usually point at the boundary between two systems, such as a load balancer and an app server, or an app server and a database. For a 502, proxy/gateway logs and upstream health are often more useful than application logs alone.


Debugging Redirect Loops

A redirect loop is the browser giving up after repeatedly being told to go somewhere else, usually back to where it started. A few concrete, common causes:

HTTP → HTTPS redirect at the app level
  +
HTTPS → HTTP redirect at a proxy/CDN level (misconfigured, expecting the opposite)
  =
Infinite loop between the two layers, each one "fixing" what the other just undid

The fastest way to find the loop is curl -IL, following redirects one hop at a time and printing each Location header. The browser often only shows "too many redirects" without the full chain:

curl -IL https://suriyaprakash.in

A common cause behind CDN/proxy setups: the CDN terminates TLS and forwards plain HTTP to the origin, but the origin application also has "force HTTPS" middleware enabled. The origin sees an HTTP request, redirects to HTTPS, the CDN receives that redirect and reissues an HTTP request to the origin again.


Debugging DNS Issues

Revisiting Part 3's toolkit: first check whether the authoritative record is correct, or whether a cache somewhere in the chain is still holding a stale answer.

dig suriyaprakash.in @1.1.1.1        # bypass local cache, check a public resolver directly
dig +trace suriyaprakash.in           # walk the full root → TLD → authoritative chain

If a public resolver query already shows the correct current answer, the problem may be a local or intermediate cache, such as the browser, OS, router, or user's ISP resolver. Re-editing the authoritative record will not make that cache expire faster.


Debugging TLS/SSL Errors

Revisiting Part 4's checklist directly, the fastest differentiator is whether the failure happens everywhere or only in specific clients:

Fails in browser too           →  likely a genuinely expired/misconfigured/mismatched certificate
Works in browser, fails in curl/mobile/backend  →  often a missing intermediate certificate
openssl s_client -connect suriyaprakash.in:443 -servername suriyaprakash.in -showcerts

This command shows the chain being served, the expiry dates, and the domains covered. That helps separate server configuration problems from client trust-store or policy differences.


Debugging CORS Errors

Revisiting Part 2: the browser console often names the missing or mismatched header. Read it precisely instead of treating every case as "CORS is broken."

"No 'Access-Control-Allow-Origin' header"        →  server isn't sending the header at all
"credentials flag is true, but ... is '*'"        →  server can't use * with credentials, needs exact origin
"Method PUT is not allowed"                       →  preflight response is missing PUT in allowed methods

Check the preflight OPTIONS request specifically in the Network tab, not just the main request. If the preflight fails or is missing a required header, the actual request may never be sent by the browser.


Debugging Hydration Mismatches

Revisiting Part 6: the browser console often names the mismatched element. The recurring root causes are narrow enough to check in order:

1. Date.now() / Math.random() / non-deterministic values used during render
2. Conditionally rendering based on a browser-only API (window, localStorage) during SSR
3. Server and client receiving genuinely different data for the same render

A hydration mismatch that only appears in production, not locally, often points at cause #3: a race condition or timing difference in the data available at server-render time versus client-render time.


Debugging Intermittent/Flaky Issues

Intermittent issues are hard because they resist simple reproduction. Instead of only trying to reproduce them directly, look for what varies between working and failing cases:

Does it correlate with a specific server instance?     →  one unhealthy node behind a load balancer
Does it correlate with load/time of day?                 →  connection pool or resource exhaustion under load
Does it correlate with a specific user/network/browser?  →  split-horizon DNS, regional CDN node, or client-specific bug
Does it correlate with cache state?                       →  stale cache on some layer, fresh on others

Correlation IDs (Part 5) make this tractable in a distributed system. Without one, you end up comparing timestamps across unrelated log streams and guessing whether they belong to the same request.


Using Browser DevTools Effectively

A few specific DevTools habits worth building deliberately, each mapping to a part of this series:

Network tab, "Preserve log" enabled  →  survives redirects/navigations without losing the trail
Network tab, check "Timing" per request  →  shows the exact waterfall breakdown per request
Console  →  reads exact CORS/hydration/mixed-content errors verbatim, don't paraphrase them
Performance panel  →  shows actual Layout/Paint/Composite blocks and flags forced reflows
Application tab  →  inspect cookies (SameSite, Secure, HttpOnly) and cache storage directly

Read the exact error text. Browsers are specific about what failed and why. "The certificate for this site expired" and "the certificate for this site is not trusted" point at different problems. Treating both as "an SSL error" throws away useful information.


Using curl and Command-Line Tools

Command-line tools matter because they skip some browser fallbacks and helpful behaviors, such as fetching missing intermediate certificates. That makes them useful for isolating whether a problem is browser-side or server-side.

curl -v https://suriyaprakash.in                  # full request/response detail, including headers
curl -IL https://suriyaprakash.in                 # follow redirects, show each hop's headers only
curl --resolve suriyaprakash.in:443:1.2.3.4 https://suriyaprakash.in   # test against a specific IP directly, bypassing DNS
openssl s_client -connect host:443 -showcerts     # inspect the exact TLS certificate chain served
dig +trace domain                                  # walk the full DNS resolution chain

The --resolve flag on curl lets you send a request to a specific IP while still sending the correct Host header and SNI. That is useful for testing a new server before cutting over DNS.


Reading Server Logs and Traces Effectively

Revisiting Part 5's observability model directly: logs, metrics, and traces each answer a different question, and debugging effectively means reaching for the right one rather than starting with whichever is easiest to open.

"What happened for this one request?"             →  logs, filtered by correlation ID
"Is this getting worse over time, or a one-off?"    →  metrics (error rate, p99 latency)
"Where specifically was the time spent?"             →  a trace across every service the request touched

A trace is disproportionately valuable the moment more than one service is involved, because it's the only one of the three that directly answers "where," rather than "that" or "how often."


A Triage Order That Works

The short explanation:

I'd check the browser console and server logs to see what's failing.

That is a useful starting point, but it does not tell you where to look first. I would add:

  • A specific triage order: layer, scope (everyone vs some), and consistency (constant vs intermittent) before touching any tool
  • Naming the exact status code family without treating it as automatic proof of who's at fault
  • Distinguishing a genuinely broken certificate from a missing-intermediate problem using a specific command
  • Recognizing that "it's already fixed" DNS complaints are often a caching-layer question, not a configuration one
  • Using correlation IDs and traces specifically once more than one service is involved, rather than comparing raw log timestamps by hand

FAQ

What's the very first thing to check when a page is slow?

The Network tab's waterfall for that specific request. It breaks the total time into DNS, TLS, TTFB, download, and rendering phases. The largest phase tells you where to investigate next.

How do I know if a 5xx error is my server's fault or something behind it?

500 usually points at the origin server itself. 502 and 504 point at the boundary between a proxy/gateway and whatever sits behind it: a database, another service, or an app server. The best logs to check first depend on the specific code.

Why does a redirect loop happen even though each individual redirect looks correct on its own?

Because two layers can each undo what the other just did. A common example is a CDN forwarding HTTP to the origin while the origin's force-HTTPS logic redirects that request back to HTTPS.

Is "it's already fixed but still broken for me" usually a real DNS problem?

Often it is a cache, such as the browser, OS, router, or your ISP resolver, that has not expired yet. Confirm the authoritative record before changing DNS again.

Why does a CORS error sometimes appear even when the server code looks fine?

Because the browser enforces CORS based on the response headers sent for that specific request, including the OPTIONS preflight. The console message often names the header at fault, so read it literally.

What's the fastest way to tell if a TLS problem is server-side or client-side?

Check whether it fails in the browser too, or only in non-browser clients like curl, a mobile app, or a backend service. Failing everywhere usually means a certificate or server configuration problem. Failing only outside the browser often means a missing intermediate certificate or a client trust-store difference.

Why are intermittent bugs harder to debug than consistent ones, and how should that change my approach?

Because they resist direct reproduction. The effective strategy shifts from "try to reproduce it" to "find what correlates with it happening," such as server instance, load, user/network, or cache state.


Glossary

TermSimple meaning
WaterfallThe Network tab's visual breakdown of a request's timing phases
TTFBTime to First Byte, how long until the server starts responding
Correlation IDA unique identifier threaded through logs to trace one request end to end
PreflightAn OPTIONS request the browser sends before certain cross-origin requests
Intermediate certificateThe certificate linking a leaf certificate to a trusted root CA
Split-horizon DNSServing different, both-correct answers depending on the requester's network
TraceA record of how a request's time was spent across every service it touched
Flaky/intermittent issueA bug that doesn't reproduce consistently, requiring correlation-based debugging

Final Mental Model

Something's broken
      ↓
Which layer owns it? (DNS → TLS → HTTP → server → rendering)
      ↓
Is it happening for everyone, or a subset (network/browser/user)?
      ↓
Is it consistent, or intermittent?
      ↓
Use the specific tool for that layer:
  dig / curl --resolve      → DNS
  openssl s_client          → TLS
  Network tab + console     → HTTP, CORS
  Logs + metrics + traces    → server
  Performance panel + console → rendering, hydration
      ↓
Fix the layer at fault, not the downstream symptom

That last line is the point of the series: many confusing production issues stay confusing because they are diagnosed one layer away from where they start. DNS problems get treated as server problems. Server problems get treated as frontend bugs. CORS gets "fixed" from the client when the missing header was on the server. Finding the layer that owns the symptom is most of the work.

About the author

Suriyaprakash Somu is a full-stack developer from Erode, Tamil Nadu, building production-ready business applications with React, Node.js, Fastify, PostgreSQL and MySQL. He focuses on Access Control, schema-based forms, and reliable backend workflows.