sitekits.dev
http

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

§01 FIELD GUIDE

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

ClassMeaningWhat the client should doHeuristically cacheable
1xxInterim (100, 101, 103)Keep waitingNo
2xxSuccessUse the body (204 has none)200, 203, 204, 206
3xxRedirect / revalidateFollow Location; reuse cache on 304300, 301, 308
4xxWrong as sentFix it; do not retry blindly404, 405, 410, 414
5xxServer failed a valid requestBack off; honour Retry-After501 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

CodePermanenceMethod and body preservedUse for
301PermanentNo — may become GETRenamed URL, host consolidation
302TemporaryNo — same rewrite riskLegacy default; avoid for POST
303TemporaryNo — forces GETPOST then redirect to a result
307TemporaryYesTemporary API endpoint move
308PermanentYesPermanent 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

DirectiveGovernsNote
max-age=NFreshness in seconds, any cache0 forces revalidation
s-maxage=NFreshness for shared caches onlyOverrides max-age at the CDN
no-cacheStore yes; revalidate before reuseNot no-store
no-storeForbids storing the responseAuthenticated HTML
privateBrowser may store, shared caches may notPersonalised responses
must-revalidateNo stale serving on errorPair with max-age
immutableNever revalidate while freshContent-hashed assets
stale-while-revalidate=NServe stale, refresh in backgroundSmooths 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.comhttps://example.comhttps://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.

FAQ
Why did my POST turn into a GET after a redirect?
Because 301 and 302 permit clients to rewrite the method to GET and drop the body, and browsers do exactly that. Use 307 for a temporary move and 308 for a permanent one when the method and body must survive the hop. A form or webhook that silently loses its payload is usually hitting an http-to-https or trailing-slash redirect that was configured as 301.
Why do the response headers in DevTools differ from what my server config says?
What DevTools shows is the response after every hop. A CDN, reverse proxy, or service worker can add, rewrite, or strip headers, HTTP/2 and /3 lowercase every field name and drop the reason phrase, and a row marked 200 (from disk cache) is not a fresh response at all. Fetching the URL server-side removes those variables and shows what the origin emitted for the final URL in the chain.
Does Cache-Control no-cache stop browsers from storing my page?
No. no-cache permits storage but requires revalidation with the origin, normally via ETag and If-None-Match, before a stored copy is reused. no-store is the directive that forbids writing the response to disk. Authenticated HTML usually wants private, no-store, while content-hashed static assets want a long max-age plus immutable.
Why does my API work in curl but fail in the browser with a CORS error?
CORS is enforced by the browser, not the server, and curl ignores it entirely. For anything beyond a simple request the browser first sends an OPTIONS preflight, and it refuses to expose the response unless the origin returns a matching Access-Control-Allow-Origin, plus Access-Control-Allow-Credentials when cookies are involved. The server often answers 200 perfectly well; you simply cannot read it. Inspect the OPTIONS response first.
Why is my Strict-Transport-Security header being ignored?
Because browsers only honour HSTS when it arrives over HTTPS. A policy served on a plain-HTTP response is discarded outright — as are any Secure cookies set there — so the port-80 response should carry the upgrade redirect and nothing else, with the security headers attached to the HTTPS response. Check every hop rather than the final 200: a header present at the end of a redirect chain says nothing about what the first hop sent. Remember that includeSubDomains and preload commit every subdomain to HTTPS for the max-age you published, and removal from the preload list takes months.