Proxy-Cheap
Proxies & Business
August 18, 2026
7 min

Are HTTP headers case sensitive?

Alex Sadovskij
Alex Sadovskij
CEO Proxy-Cheap
Are HTTP headers case sensitive?
Summary
HTTP header names are always case-insensitive, so Content-Type, content-type, and CONTENT-TYPE all match, though HTTP/2 and HTTP/3 send them lowercase on the wire. Header values are different: some, like media types, ignore case, while others, like cookies, ETags, and tokens, are case-sensitive and must match exactly.
  • HTTP header names are case-insensitive in every version of the protocol. Content-Type, content-type, and CONTENT-TYPE all reference the same field, and a compliant server must treat them as equal.
  • HTTP/2 and HTTP/3 require field names to be sent in lowercase. Messages with uppercase names are treated as malformed. Your client library usually handles the conversion for you.
  • Header values are a separate question. Some are case-insensitive (MIME types, directive tokens), while others are case-sensitive (cookie values, ETags, Authorization credentials, base64 data).
  • Proxies, middleware, and request libraries often normalize header casing. Keeping casing consistent across your stack makes requests predictable during QA and localization testing.

The short answer: names, values, and protocol version

Header names are case-insensitive, so casing does not change which field a name refers to. Header values depend on the specific header. Content types and directive tokens ignore case, while cookies, ETags, and credentials do not. HTTP/1.1 accepts any casing on names. HTTP/2 and HTTP/3 require lowercase names on the wire. When in doubt, keep casing consistent everywhere.

That is the whole rule in three parts. Names are case-insensitive. Values are case-sensitive only for some headers. The protocol version determines the casing sent over the wire.

AxisRule
Header namesCase-insensitive
Header valuesDepends on the header
Names in HTTP/2 and HTTP/3Must be lowercase on the wire

Are HTTP header names case sensitive?

No. HTTP header field names are case-insensitive by specification. RFC 9110, the current HTTP semantics standard, defines field names as case-insensitive tokens, so User-Agent, user-agent, and USER-AGENT are the same field. The rule is not new: RFC 2616 stated it in section 4.2 back in 1999, and RFC 7230 restated it. A server that reads names case-sensitively is not following the standard.

The current source of truth is RFC 9110, published in June 2022. It defines a field name as a case-insensitive token, which means the lookup a server performs on a header name must ignore case. This has been consistent across every revision of HTTP. RFC 2616 said the same thing in 1999, and RFC 7230 repeated it. So this is a long-standing rule, not a recent change.

Title-Case with hyphens, such as Cache-Control or Content-Type, is a readability convention only. It is easy to read, so most tools and documentation use it, but the specification does not require it. You can send content-type or CONTENT-TYPE, and a compliant server treats them identically.

The caveat is real-world implementations. Some servers still read names case-sensitively against the spec, which produces hard-to-spot bugs. One documented case involved a server that read content-length case-sensitively and returned an error when the name did not match its expected casing. The request was valid; the server was wrong. Bugs like this are why consistent casing is worth the small effort.

We tested it: what casing actually reaches the server

Rules are easier to trust when you can see them. We sent the same headers with deliberately mixed casing, first over HTTP/1.1 and then over HTTP/2, and captured the actual header lines each protocol put on the wire. Here are the two commands:

bash # HTTP/1.1: the casing you type is preserved on the wire curl --http1.1 -v -H "X-Test-Header: value" -H "content-type: application/json" https://example.com -o /dev/null # HTTP/2: names are lowercased on the wire curl --http2 -v -H "X-Test-Header: value" -H "content-type: application/json" https://example.com -o /dev/null

Over HTTP/1.1, the verbose request lines came back exactly as typed:

text > X-Test-Header: value > content-type: application/json

Over HTTP/2, the same request went out with every name lowercased, and the request opened with colon-prefixed pseudo-headers:

text > :method: GET > :path: / > :scheme: https > :authority: example.com > x-test-header: value > content-type: application/json

The casing you write in your code is not always the casing that travels on the wire. HTTP/1.1 keeps it; HTTP/2 lowercases it. You can confirm this against an HTTP echo endpoint that returns the headers it received, which is a quick way to check your own data collection workflows. Routing the same request through a residential or datacenter proxy for location-accurate testing adds one more layer, and that intermediary may normalize the casing again before it reaches the origin.

Header as typedOn the wire (HTTP/1.1)On the wire (HTTP/2)Through a proxy
X-Test-HeaderX-Test-Headerx-test-headerMay be normalized
content-typecontent-typecontent-typeMay be normalized

Are HTTP header values case sensitive?

It depends on the header. Media types and directive tokens are case-insensitive, so Content-Type: text/html equals TEXT/HTML, and Cache-Control: no-cache equals NO-CACHE. Other values are case-sensitive and must match exactly: cookie names and values, ETags, Authorization credentials and tokens, base64-encoded data, and the path portion of a URL. When a value is an opaque identifier, treat it as case-sensitive.

The table below is the practical reference. It separates the values that ignore case from the ones that do not.

Header or value typeCase-sensitive?Notes
Field names (all headers)NoCase-insensitive per RFC 9110
Content-Type media type (text/html)NoType, subtype, and parameter names are case-insensitive
Cache-Control and Connection directives (no-cache, keep-alive)NoDirective tokens are case-insensitive
Cookie names and values (Set-Cookie, Cookie)YesTreated as opaque octets (RFC 6265)
ETag valuesYesOpaque, compared byte for byte
Authorization credentials and tokensScheme no, credentials yesBearer vs bearer is fine; the token after it is case-sensitive
base64-encoded valuesYesThe base64 alphabet is case-sensitive
URL in Location or path segmentsPath yes, host noHostnames are case-insensitive; the path is case-sensitive

The underlying principle is simple. Values that are human-readable tokens defined by the spec are usually case-insensitive. Values that are opaque identifiers or encoded data are case-sensitive because software on both ends compares them byte for byte. This causes a common problem: a token or cookie looks right but fails because one character differs in case. If you build against the Proxy-Cheap API, the same rule applies to its authentication headers, where the key and secret values must match exactly.

How HTTP/2 and HTTP/3 change the rules

HTTP/2 and HTTP/3 keep names case-insensitive in meaning but require them to be lowercase on the wire. RFC 9113 section 8.2 states field names must be converted to lowercase, and a message with an uppercase name is treated as malformed. HTTP/2 also adds pseudo-headers, prefixed with a colon (:method, :path, :scheme, :authority, :status), which are always lowercase.

RFC 9113, section 8.2 is explicit: a field name must not contain uppercase characters, and a request or response that carries one is malformed. The pseudo-headers are the other HTTP/2 addition. Requests carry :method, :path, :scheme, and :authority; responses carry :status. They are colon-prefixed, always lowercase, and sent before the regular fields, as our tested output showed above.

In practice, this rarely affects your code. A compliant HTTP/2 or HTTP/3 library lowercases names for you, so writing Content-Type in your source still works exactly as expected. The only place the rule bites is manual byte-level comparison, where you read raw header keys yourself. The MDN HTTP headers reference reflects this too, displaying names in lowercase for HTTP/2. As of 2025, some APIs have started advertising lowercase names directly, which is a housekeeping change rather than a new requirement.

How proxies and middleware handle header casing

A request often passes through several layers before reaching the server: a client library, a proxy, and sometimes middleware. Each layer may normalize, reorder, or rewrite header casing independently. This is why the same code can produce different casing on the wire in different environments. The fix is not clever casing but consistent casing across every layer of your stack.

The path is usually library, then proxy, then middleware, then server. Any of these can normalize casing before passing the request along. The common symptom is confusing: headers look one way in your code and another when captured on the wire. This slows debugging. The remedy is to standardize on a single casing convention across the stack and capture the wire output once during setup so you know exactly what gets sent.

This matters most when reliability is the goal. When you route requests through datacenter proxies or static residential proxies for location-accurate testing, QA, or SEO and SERP data collection across regions, consistent header handling keeps those requests predictable. Datacenter proxies suit high-throughput crawls of documentation and public content. Static residential proxies suit location-accurate testing that needs a consistent identity. In both cases, consistent casing removes one variable from your debugging.

Most libraries normalize header names for you, so you rarely think about casing unless you read or compare header keys by hand. The table below summarizes how common tools behave. Verify each row against the current library documentation before you rely on it, since defaults change across versions.

Library or toolCasing behavior on namesHTTP/2
Python requestsTitle-Cases names, case-insensitive accessDepends on the transport (urllib3)
Python httpxLowercases names, case-insensitive accessSupported
Python aiohttpCase-insensitive multidict accessSupported
Node.js fetch / node-fetchLowercases names in the Headers APISupported
ScrapyPreserves the casing you setAdjust when targeting HTTP/2
Java HttpClientCase-insensitive accessSupported
Go net/httpCanonicalizes names to Title-Case (CanonicalMIMEHeaderKey)Supported
cURLPreserves case on HTTP/1.1, lowercases on HTTP/2Supported

The practical takeaway is that the library handles the wire format, so your Title-Case names stay valid regardless of protocol. The one gotcha is your own code. If you compare raw header keys with a case-sensitive match, you can miss a header that arrived in a different case, which is where these bugs appear. Picking the right tooling starts with choosing the right proxy type and pairs well with the Chrome proxy extension for quick manual checks.

Best practices for consistent header casing

A short checklist keeps casing from ever becoming a problem:

  • Use lowercase names when you target HTTP/2 or HTTP/3 services. Most libraries do this for you, but verify it during setup.
  • Keep one casing convention across your whole stack, covering client, proxy, and middleware, so requests stay predictable.
  • Treat opaque values such as cookies, tokens, ETags, and base64 as case-sensitive, and copy them exactly.
  • Access header keys case-insensitively in your own code. Never compare raw keys with a case-sensitive match.
  • Capture the wire output once during setup so you know exactly what your requests send.

Reliable, consistent requests matter most when you route through proxies for QA or localization testing. Proxy-Cheap offers residential, ISP, and datacenter proxies on pay-as-you-go and per-IP plans for that kind of testing, so you can start small and scale as a project needs it. See Proxy-Cheap for the current product lineup.

Frequently Asked Questions

The Content-Type name is case-insensitive, and so is its media type value. Content-Type: text/html, content-type: TEXT/HTML, and any mix of the two mean the same thing. Parameter names such as charset are also case-insensitive, though some parameter values may not be.

The header name and the scheme keyword (Bearer, Basic) are case-insensitive, so bearer and Bearer both work. The credentials that follow are case-sensitive and must match exactly, because tokens and base64 data use a case-sensitive alphabet. Copy the token exactly as issued.

The Cookie and Set-Cookie header names are case-insensitive, but cookie names and values are case-sensitive. sessionid and SessionID are two different cookies. Match the exact case your server set.

No. You can still write Content-Type in your code. A compliant HTTP/2 library converts names to lowercase before sending them, as RFC 9113 requires. The only place this matters is if you manually read or compare header keys with a case-sensitive match.

Yes. HTTP methods are case-sensitive and are defined in uppercase, so GET is valid and get is not. This is different from header names, which are case-insensitive. Most libraries send the correct uppercase method for you.

Partly. In a URL, the scheme and hostname are case-insensitive, but the path and query are case-sensitive. So a Location header pointing to /Page is different from /page, even though the domain casing does not matter.

That server is reading header names case-sensitively, which is against the HTTP specification. Field names are case-insensitive, so a compliant server must accept content-length and Content-Length equally. If you control the server, fix the lookup to compare names case-insensitively; if you do not, match the exact case it expects as a workaround.

They can. A proxy or middleware layer may normalize, reorder, or rewrite header casing as a request passes through, so the casing your code sets is not always the casing that reaches the server. Keep casing consistent across your stack and capture the wire output during setup so you know what gets sent.

Either is valid, because names are case-insensitive. Title-Case (Content-Type) is the readable convention for HTTP/1.1, and lowercase is required on the wire for HTTP/2 and HTTP/3. The most reliable choice is to pick one convention and apply it consistently everywhere.

Run your request with verbose output (for example, curl -v) and read the request-header lines, or send it to an HTTP echo endpoint that returns the headers it received. This shows the real casing on the wire, which is often normalized differently from what you wrote. If you are also debugging connectivity, you can first find your proxy server address.