HTTP Headers

HTTP headers are named fields that travel with every request and response and describe the message rather than carry its data. As in the lesson on requests and responses, they sit between the start line and the body as Name: value lines; RFC 9110 calls them header fields. They say what format the body is in, who is asking, how long the answer may be cached and what to remember.

For example, when a shop's app saves a customer's language, the request carries Content-Type: application/json (this body is JSON), Accept: application/json (answer in JSON) and Authorization: Bearer … (this is who I am). The response adds Cache-Control: no-store (keep no copy) and Set-Cookie: lang=ar (remember this). None of it is on screen, yet each line decides whether the feature works and is safe.

Why it matters

Many defects live only in headers. A correct JSON body labelled Content-Type: text/html breaks clients that trust the label. A personal profile sent with Cache-Control: public may be stored by a shared cache, such as a CDN (content delivery network), and served to the next user. A session cookie without Secure may travel over unencrypted HTTP. Each time, the screen looks right and the UI test passes.

Headers also explain failures fast: Content-Type why a body does not parse, WWW-Authenticate which authentication a refused call expected, Cache-Control and Age (how many seconds old a cached copy is) why a fixed page still looks old.

How headers are written and read

Field names are case-insensitive: Content-Type and content-type are the same header, and HTTP/2 and HTTP/3 send every name in lowercase, which is why DevTools often shows content-type. Whether a value is case-sensitive depends on the header: media types such as application/json are not; access tokens and cookie values are.

A header may appear more than once. For list-valued headers such as Accept, a recipient may join the lines into one comma-separated line; RFC 9110 names Set-Cookie as the exception: one cookie per line, never merged. Headers fall into groups:

  • Request headers describe the client and what it wants: Host (required in HTTP/1.1), User-Agent, Accept, Accept-Language, Authorization, Cookie.
  • Response headers describe the answer: Set-Cookie, Location, WWW-Authenticate, Retry-After, Server.
  • Content headers describe the body in either direction: Content-Type, Content-Length, Content-Encoding. Cache-Control also works both ways, with a different meaning in each.

Try it: Request Header Inspector Try it: HTTP Headers Parser

Five headers in almost every test

The exchange below saves a customer's preferred language on a test shop. Each of the five headers answers a different question.

Saving a language preference: Content-Type labels the body in both directions, Accept and Authorization go with the request, and the response answers with Cache-Control: no-store and a Set-Cookie.

Request

PUT /api/v1/me/preferences HTTP/1.1
Host: test.shop.example.com
Part 1:Content-Type: application/json
Part 2:Accept: application/json
Part 3:Authorization: Bearer eyJhbGciOi...
Content-Length: 18

{"language": "ar"}

Response

HTTP/1.1 200 OK
Part 1:Content-Type: application/json; charset=utf-8
Content-Length: 55
Part 4:Cache-Control: no-store
Part 5:Set-Cookie: lang=ar; Path=/; Max-Age=31536000; Secure

{"language": "ar", "updatedAt": "2026-10-01T09:30:00Z"}
  1. 1Content-TypeLabels the body of each message: JSON going out, JSON with a charset coming back. It must match the bytes; a format the server does not accept gets 415.request and response
  2. 2AcceptWhat the client will take back. If nothing matches: 406, or the server's default format, as the contract says.request
  3. 3AuthorizationA scheme and the credentials. Missing or invalid: 401 with WWW-Authenticate. Mask the value in every report.request
  4. 4Cache-Controlno-store: no cache may keep a copy, the right rule for personal data. no-cache would still allow storing with revalidation.response
  5. 5Set-CookieStores lang=ar for a year; the browser sends it back in Cookie. Secure keeps it off unencrypted HTTP.response
  • Content-Type: the media type of this message's body, with optional parameters such as charset. The receiver parses the body by it, so it must match the bytes; a server that does not accept the format should answer 415 Unsupported Media Type.
  • Accept: the media types the client will take, optionally weighted (q=0.8). If none is available, RFC 9110 lets the server answer 406 Not Acceptable or ignore the header and send its default; the contract should say which.
  • Authorization: a scheme and credentials, such as Bearer <token> (a token issued at sign-in) or Basic <credentials> (user name and password joined by a colon). Missing or invalid credentials get 401 Unauthorized, which RFC 9110 requires to carry a WWW-Authenticate header. Basic credentials are Base64-encoded, not encrypted: anyone who sees them can read them.
  • Cache-Control: directives for caches (RFC 9111). max-age=600: fresh for 600 seconds, so a cache may reuse it without asking the server. no-cache: may be stored, but must be revalidated before each reuse. no-store: must not be stored. private: only a private cache, such as the browser, may store it. The HTTP caching lesson goes deeper.
  • Set-Cookie: asks the browser to store a cookie, which it returns in the Cookie header of later matching requests. Attributes such as Max-Age, Secure and HttpOnly control how long it lives, where it goes and who can read it; the cookies and sessions lesson tests them.
Text version
PUT /api/v1/me/preferences HTTP/1.1
Host: test.shop.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOi...
Content-Length: 18

{"language": "ar"}

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 55
Cache-Control: no-store
Set-Cookie: lang=ar; Path=/; Max-Age=31536000; Secure

{"language": "ar", "updatedAt": "2026-10-01T09:30:00Z"}

Try it: Content-Type Validator Try it: HTTP Header Builder

What to test in headers

Headers are part of the contract, so they belong in the test basis and the expected results.

  • Response checks: the documented Content-Type, with a body that really is in that format; Location after 201 Created if the contract promises it; Cache-Control: no-store on personal data and credentials; the agreed attributes on every Set-Cookie.
  • Request tests, one change at a time: no Authorization or an expired token (401 with WWW-Authenticate); JSON sent as Content-Type: text/plain (415, or the documented 400); an Accept format the API lacks (406, or the documented default). A duplicated header or a huge value may get 400 or 431 Request Header Fields Too Large; a 500 or a crash is a defect.
  • Leaks and security headers: Server: Apache/2.4.41 (Ubuntu) or X-Powered-By: PHP/7.4.3 tell attackers which versions to look up; the OWASP HTTP Headers Cheat Sheet recommends removing X-Powered-By and removing Server or giving it a non-informative value. Check the agreed security headers, such as Strict-Transport-Security (it tells browsers to reach the site over HTTPS only), only on sites you own or have written permission to test.

Try it: API Header Validator Try it: Security Header Analyzer

Worked example: a profile response

A tester checks the profile endpoint of a shop's web app on the test environment. The status is 200 and the body is right, so the UI test passes, yet the raw exchange holds four header defects, each in its own report because each may be fixed in a different place: application code, session settings or web server configuration.

  • Content-Type: text/html on JSON: clients that follow the header may not parse it as JSON, and a browser renders it as HTML, a script injection risk once a field holds user input. Expected: application/json.
  • Cache-Control: public, max-age=3600 on personal data: a shared cache such as a CDN or a company proxy may keep the profile for an hour and serve it to someone else. Expected: no-store.
  • Set-Cookie sets the session cookie again without Secure and HttpOnly: it may travel over unencrypted HTTP and page scripts can read it. Expected: the agreed session cookie attributes.
  • Server and X-Powered-By reveal exact versions. Expected: removed or generic.
  • After the fixes, confirmation testing repeats the request and compares the headers, and the four checks join the API regression suite.
GET /api/v1/me HTTP/1.1
Host: test.shop.example.com
Accept: application/json
Cookie: sid=9f2c41d7e0

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 62
Cache-Control: public, max-age=3600
Set-Cookie: sid=9f2c41d7e0; Path=/
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: PHP/7.4.3

{"id": 58, "name": "Test User", "email": "user58@example.com"}

Try it: HTTP Headers Parser Try it: Cache Header Analyzer Try it: Cookie Analyzer

Common mistakes

  • Checking the status code and the body but never the response headers.
  • Comparing header names case-sensitively in automated tests, so a move to HTTP/2 breaks the suite without any defect.
  • Reading no-cache as "do not cache": it allows storing with revalidation. The directive that forbids storing is no-store.
  • Assuming the request holds only the headers you typed: browsers and API clients add User-Agent, Cookie and others.
  • Pasting real Authorization or Cookie values into reports or screenshots: anyone who copies them can act as that user. Mask them.

The QA perspective

Headers come up in review, in testing and after fixes. In contract review, ask which headers each endpoint needs and returns, what an unsupported Accept or Content-Type gets, and how long each response may be cached; a silent contract is your first finding. During testing, keep the raw request and response with every API result.

Turn each fixed header defect into a regression check. Ask DevOps what the web server, gateway or CDN adds or strips, because a header can be right in the code and changed on the way. Mask credentials in all evidence.

Practice exercise

Review the headers of an address change

Your task

A test shop's mobile app changes the delivery address of an order. The contract says: JSON bodies only; an unsupported Content-Type gets 415 and an unsupported Accept gets 406; Basic authentication over HTTPS; responses with personal data carry Cache-Control: no-store.

Review only the headers below; the response body is correct. List each problem with the expected value and the risk, then write three negative tests that each change one request header. The Mutqan Base64 Decoder decodes the Authorization value.

Material
PATCH /api/v1/orders/88121/address HTTP/1.1
Host: test.shop.example.com
Content-Type: text/plain
Accept: application/xml
Authorization: Basic dGVzdGVyMTpUZXN0LVBhc3MtMTIz
Content-Length: 41

{"city": "Jeddah", "postalCode": "23436"}

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 59
Cache-Control: no-cache
Set-Cookie: lastCity=Jeddah; Path=/; Max-Age=31536000
Server: nginx/1.18.0

{"orderId": 88121, "city": "Jeddah", "postalCode": "23436"}
Steps
  1. Sort the headers into request and response.
  2. Compare each Content-Type with its body and the contract; check Accept.
  3. Decode the credentials; check the caching, cookie and information headers.
  4. Write three negative tests, one request header each.
Show the model answer
Model answer

Request headers: Host, Content-Type, Accept, Authorization, Content-Length. Response headers: Content-Type, Content-Length, Cache-Control, Set-Cookie, Server. Host, both Content-Length values (41 and 59 bytes) and the response Content-Type are correct.

Content-Type: text/plain on JSON: the app sends the wrong label (expected application/json), and the server accepted it instead of answering 415. Risk: the app breaks as soon as the server enforces the contract. Two defects, two reports.

Accept: application/xml got JSON with 200. RFC 9110 allows either reaction, but this contract chose 406, so the 200 is a defect. Risk: a client that asked for XML cannot read the answer. Also ask why the app asks for XML at all.

The Basic value decodes to tester1:Test-Pass-123: Base64 is not encryption. Risk: anyone who sees the request can sign in as tester1. Send it over HTTPS only, mask it in all evidence, and change a password seen in a shared report.

Cache-Control: no-cache breaks the no-store rule: a cache may still keep the address if it revalidates before reuse. Expected: no-store.

Set-Cookie: lastCity=Jeddah keeps part of the address for a year without Secure, so it may travel over unencrypted HTTP: ask whether it is needed, and if so add Secure. Server: nginx/1.18.0 reveals the version attackers would look up. Expected: removed or generic.

Negative tests, each starting from a valid request (application/json in Content-Type and Accept, valid credentials) and changing one header: (1) no Authorization → 401 with a Basic WWW-Authenticate, address unchanged; (2) the same JSON body with Content-Type: application/xml → 415, address unchanged; (3) Accept: image/png → 406, never 500.

Frequently asked questions

Are header names case-sensitive?

No. RFC 9110 defines field names as case-insensitive, and HTTP/2 and HTTP/3 send them in lowercase. Values are another matter: some are case-insensitive, such as media types and the charset parameter (UTF-8 equals utf-8), and others are not, such as access tokens, cookie values and, unless their documentation says otherwise, custom values.

What is the difference between Accept and Content-Type?

Content-Type describes the body that is in this message; Accept describes what the client wants in the response. A request that sends JSON and wants JSON back carries both: Content-Type: application/json and Accept: application/json. A GET request normally has no body, so it may carry Accept but has no reason to carry Content-Type.

Where can I see the headers of a request?

In the browser, open DevTools, select the request in the Network panel and open its Headers tab, which shows request and response headers separately. An API client shows them in a response tab, curl -i prints the response headers and curl -v prints both directions. The Mutqan Request Header Inspector shows what your browser actually sends.

References

  • RFC 9110: HTTP SemanticsIETF · 2022The meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
  • RFC 9111: HTTP CachingIETF · 2022How Cache-Control, Expires, ETag and validators decide whether a response may be stored and reused.
  • OWASP Cheat Sheet Series: HTTP HeadersOWASP FoundationWhich security headers to send (HSTS, CSP, X-Content-Type-Options, frame protection) and which to remove (Server, X-Powered-By).
  • HTTP headers — MDNMDN Web Docs (Mozilla)Reference for request and response headers: syntax, directives and browser support.

Practise with Mutqan tools

Parse raw HTTP request/response headers into a structured table with explanations.

Web

Inspect the request headers your browser or client sends, with explanations for each.

API Testing

Validate API response headers for correctness, security and caching best practices.

API Testing

Validate Content-Type headers and check that a response body actually matches its declared media type.

Web

Analyze Cache-Control, Expires, ETag, Last-Modified, Vary and Age headers and explain how browsers and CDNs will cache the response.

Validation

Decode Base64 or Base64url strings to text, with automatic padding repair.

Encoding

Go deeper

Your progress on this topic

Part of the QA Roadmap