HTTP tools
A working reference for HTTP debugging: status and redirect semantics, Cache-Control directives, and which tool to reach for when headers, URLs, or share cards misbehave.
9 tools
What you are actually debugging
An HTTP exchange is four separable things, and most wasted debugging time comes from mixing them up: the URL (what you asked for), the request headers (who you claim to be and what you accept), the status line (the verdict), and the response headers (the contract for caching, security, and content type). The body is the least interesting part — by the time it is wrong, the headers have already explained why.
Second, almost nothing a browser shows you is the origin’s raw output. Between
your server and the Network panel sit a CDN, maybe a proxy, HTTP/2 or /3 (which
lowercase field names and drop the reason phrase), a service worker, and the disk
cache. A 200 OK (from disk cache) row is a memory of a response, not a
response — so when a header looks missing, first ask whose copy you are reading.
Third, HTTP is largely self-reported: User-Agent, Referer,
Accept-Language, and every custom X- header are claims a client chose to
send, forgeable in one line of code. Only what the origin emits — the status code
and the response headers — is yours to control.
Status classes at a glance
| Class | Meaning | What the client should do | Heuristically cacheable |
|---|---|---|---|
1xx | Interim (100, 101, 103) | Keep waiting | No |
2xx | Success | Use the body (204 has none) | 200, 203, 204, 206 |
3xx | Redirect / revalidate | Follow Location; reuse cache on 304 | 300, 301, 308 |
4xx | Wrong as sent | Fix it; do not retry blindly | 404, 405, 410, 414 |
5xx | Server failed a valid request | Back off; honour Retry-After | 501 only |
Two codes are routinely mishandled: 429 should carry Retry-After, and
304 Not Modified is a success — logging it as an error hides a working cache.
The redirect matrix people get wrong
| Code | Permanence | Method and body preserved | Use for |
|---|---|---|---|
301 | Permanent | No — may become GET | Renamed URL, host consolidation |
302 | Temporary | No — same rewrite risk | Legacy default; avoid for POST |
303 | Temporary | No — forces GET | POST then redirect to a result |
307 | Temporary | Yes | Temporary API endpoint move |
308 | Permanent | Yes | Permanent POST endpoint move |
Browsers also cache 301 hard, sometimes for the life of the profile, so a
mistaken one is expensive to undo — verify on staging first.
Cache-Control, directive by directive
| Directive | Governs | Note |
|---|---|---|
max-age=N | Freshness in seconds, any cache | 0 forces revalidation |
s-maxage=N | Freshness for shared caches only | Overrides max-age at the CDN |
no-cache | Store yes; revalidate before reuse | Not no-store |
no-store | Forbids storing the response | Authenticated HTML |
private | Browser may store, shared caches may not | Personalised responses |
must-revalidate | No stale serving on error | Pair with max-age |
immutable | Never revalidate while fresh | Content-hashed assets |
stale-while-revalidate=N | Serve stale, refresh in background | Smooths CDN misses |
Vary belongs here too: if a response differs by
Accept-Language or a custom header and you never declare it, a shared cache
hands the wrong variant to the next visitor.
Which tool for which question
When you have a URL and want the origin’s own answer, use the
HTTP Headers Checker: the request runs from api.sitekits.dev,
so no extension, cache, or service worker is in the path, and you get the final
status, the final URL, the hop count, and every response header. It reads headers
only, never the body, and its SSRF guard rejects localhost and private ranges.
When you need to send something specific — a PATCH with a JSON body, an
Authorization: Bearer header, a bare OPTIONS to read a preflight — use the
REST API Tester. Requests go from your browser straight to
the target, so its CORS policy applies just as it does to your own front-end
code — ideal when the bug is CORS. If the token is the suspect, paste it into the
JWT Token Decoder to read exp and the claims; decoding is not
verification.
When the failing unit is a whole page load rather than one request, export a HAR
into the HAR File Viewer; the HAR field guide
covers timings. Query-string mysteries fall to the
URL Parser & Analyzer, which lists percent-decoded parameters via
the browser’s WHATWG parser, plus URL Encode / Decode when a
value was escaped once too often. The User Agent Parser
reduces a UA string to browser, OS, device class, and a bot heuristic; unfamiliar codes go to the
HTTP Status Code Reference; a file that downloads instead of
rendering is usually a MIME Type Checker question about
Content-Type. When the response is healthy but the share card is wrong, paste
the page’s HTML into the Open Graph Preview — parsing is local, so
it works on staging builds and behind logins.
Gotchas
Strict-Transport-Security on an HTTP response does nothing
Browsers ignore HSTS delivered over plain HTTP, and Secure cookies are dropped
there too. Serve the upgrade redirect on port 80 and the security headers on the
HTTPS response — and check both hops, not just the last.
Security headers are per-response, not per-site
A hardened Content-Security-Policy on your HTML says nothing about your API’s
JSON, your CDN’s error pages, or an uploads bucket elsewhere. The
CSP Generator writes the header; your deploy config decides
which responses carry it.
Redirect chains cost round trips
http://example.com → https://example.com → https://www.example.com/ →
/home/ costs three extra round trips before any HTML, and each hop can drop a
method, cookie, or query string. Collapse it to one hop at the edge.
200 with an error in the body defeats status-based monitoring
Answering 200 while the payload says {"error": ...} hides failures from alerts
keyed on status class and inflates measured availability. If you own the SLO,
assert on the payload too — see the tools gathered for SRE work.
+ and %20 are not interchangeable
In a query string encoded as application/x-www-form-urlencoded, + decodes to
a space; in a path segment it is a literal plus. Double-encoding is the sibling
bug: %20 becomes %2520 and the break looks like routing, not escaping.