How to Normalize HTTP Headers Into One Canonical Form
Header names are case-insensitive, so Content-Type and content-type are the same field. Normalize a captured header set to canonical Train-Case before you compare or dedupe.
How to Normalize HTTP Headers Into One Canonical Form
Two engineers capture the same response. One pastes content-type: text/html, the other pastes Content-Type: text/html. Diff the two captures and you get a phantom mismatch — different casing, an extra space after the colon — even though both describe the identical field. That noise is the whole problem this post is about, and it comes straight from how the HTTP spec treats header names.
This guide walks through why header field names need a single canonical shape before you compare, dedupe, or import a header set, and how the HTTP Header Normalizer rewrites a messy capture into one consistent form locally in your browser.
Header names are case-insensitive, but your tools are not
RFC 9110 is explicit: HTTP header field names are case-insensitive. Content-Type, content-type, and CONTENT-TYPE all name the same header. A compliant server treats them identically, and a compliant client should too.
Your text tools do not get that memo. A plain string comparison, a Set for deduplication, a sort, a grep, a spreadsheet VLOOKUP — all of these treat Content-Type and content-type as two distinct strings. So the moment a header set passes through software that does not understand the spec, casing differences turn into false distinctions: duplicate rows that are not really duplicates, a sort that scatters the same field across the list, a diff that flags a change where nothing changed.
The fix is to pick one canonical casing and rewrite every name to it before any string operation runs.
Canonical Train-Case is the convention worth following
The case-insensitivity rule means any casing is technically valid, but conventions exist for a reason. The widely used form is hyphenated title case — each word capitalized, words joined by hyphens: Content-Type, Accept-Encoding, X-Forwarded-For, Cache-Control. You will see it called Train-Case or Hyphenated-Title-Case; it is the form HTTP/1.1 messages have printed for decades and the form most documentation and examples assume.
One wrinkle worth knowing: HTTP/2 and HTTP/3 require lowercase field names on the wire. So if you capture headers from a modern connection — say, from a browser devtools panel that reflects the real HTTP/2 frame — you may legitimately see content-type lowercased. That is correct for the transport. It does not change what the field is; it just means the same Content-Type field appears in a different surface form depending on where you grabbed it. Which is exactly why normalizing to one canonical form lets a lowercase HTTP/2 capture and an uppercase HTTP/1.1 capture line up field-by-field.
The HTTP Header Normalizer's target casing is canonical Title-Case. Per the tool's own description, it rewrites each field name to that form and trims the whitespace around the colon and value, so content-type: text/html and Content-Type: text/html collapse to the same normalized row.
A worked example
Suppose a teammate hands you this raw capture, copied from a log line and a devtools panel and a support ticket, all glued together:
content-type: application/json
ACCEPT-ENCODING: gzip, br
Cache-control: no-store
content-type: application/json
x-request-id: 9f3a-22b1
Run it through the normalizer and the field names snap to canonical Title-Case while the spacing around each colon and value is trimmed:
Accept-Encoding: gzip, br
Cache-Control: no-store
Content-Type: application/json
X-Request-Id: 9f3a-22b1
Notice what happened. The two content-type lines — one lowercase, one already partly cased, both with ragged spacing — became a single Content-Type row once dedupe was on, because after normalization they are byte-for-byte identical. ACCEPT-ENCODING and Cache-control were rewritten to Accept-Encoding and Cache-Control. The list is sorted, so the same field always lands in the same place across two different captures. That is the entire point: identical inputs that looked different now read the same, and the diff between two header sets reflects real differences only.
Why one form is the prerequisite for comparing or deduping
Deduplication and comparison are key-equality operations. They ask: is row A the same as row B? With raw header text the answer depends on accidental casing and whitespace, which means the answer is wrong as often as it is right. Normalize first and the key becomes the canonical field name, so equality finally means what you want it to mean.
This is also why the normalizer keeps invalid rows when you ask it to. A name with embedded spaces, a stray second colon inside the field name, or a blank line where a header should be cannot be normalized cleanly — and silently dropping those would hide a real problem. The tool keeps each broken row with its reason attached, so the cleanup stays honest. You get a clean, comparable list plus an explicit record of what could not be made clean.
When I reach for this
I keep a tab open on this tool whenever I am reconciling two captures that should match but do not. The last time was chasing a caching bug: staging served Cache-Control: no-store and production served cache-control: no-store, and a naive diff script I had written flagged them as different every single run. Rather than patch the script, I pasted both header sets in, normalized to canonical Title-Case, deduped, and the phantom difference vanished — leaving exactly one real discrepancy, a Vary header present in one environment and not the other. That was the actual bug. Everything else had been casing noise the whole time, and I had wasted an afternoon staring at it.
From clean list to the format you need
Once the header set is normalized, sorted, and deduped, you rarely want it as plain lines. The tool switches the same clean list between CSV, JSON, Markdown, SQL IN, TypeScript union, and plain output, so you can drop it straight into a fixture, a query filter, or a type without hand-adding quotes and commas. Sensitive profiles such as cards and JWTs mask their values in the output while still returning useful validation signals, so you can prepare an artifact for review without leaking the payload. All of it runs in the browser — the File API reads any uploaded capture locally and nothing is sent to a server.
If you only need to pull the header lines out of a noisy paste first, the HTTP Header Extractor handles that step, and the HTTP Header Deduplicator is the dedicated tool when collapsing repeats is all you want. For the same canonical-cleanup approach applied to environment configuration rather than headers, the Env Var Extractor follows the same browser-local pattern.
Pick one canonical form, rewrite every field name to it, and your captured header sets finally compare the way the HTTP spec always said they should.
Made by Toolora · Updated 2026-06-13