How to check HTTP response headers with curl
Dump the response headers of a real GET and drop the body: curl -s -D - -o /dev/null https://example.com/. -D - writes the headers to stdout. -i keeps headers and body together. -I is shorter but sends HEAD, and a server may answer HEAD with different headers.
Why check this
Caching, compression, cookies and every security policy are carried in headers, so a header read is the cheapest regression check after an infrastructure change. Run it after a CDN configuration edit, after a reverse proxy upgrade, and on every staging sign off. It catches the case where a new proxy strips cache-control and the CDN starts serving a logged in page to anonymous visitors.
Prerequisites
- curl 7.0 or later. See
-D, --dump-headerin the curl manual. - On Windows PowerShell or cmd, replace
/dev/nullwithNUL. - The path the client actually requests. Headers on
/and on/api/orderscome from different rules on most stacks.
Steps
- Step 1.
Dump every response header from a normal GET and discard the body.
curl -s -D - -o /dev/null https://www.cloudflare.com/HTTP/2 103 link: </fonts/Kunst%20Grotesk%20Regular.woff2>; as=font; crossorigin; rel=preload; type=font/woff2, … HTTP/2 200 date: Fri, 11 Sep 2026 18:58:46 GMT content-type: text/html; charset=utf-8 link: <https://www.cloudflare.com/.well-known/agents.json>; rel="api-catalog", … cf-ray: a398e7e259a45906-WAW cf-cache-status: HIT accept-ranges: bytes age: 1 cache-control: max-age=10 last-modified: Fri, 11 Sep 2026 18:58:45 GMT server: cloudflare vary: accept-encoding strict-transport-security: max-age=31536000; includeSubDomains content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline' … … permissions-policy: geolocation=(), camera=(), microphone=() referrer-policy: strict-origin-when-cross-origin x-content-type-options: nosniff x-frame-options: SAMEORIGIN … set-cookie: _ga=GA1.1.c5df37da-b28e-45c2-8730-889138c41aed; Expires=Mon, 11 Oct 2027 18:58:46 GMT; Path=/ … alt-svc: h3=":443"; ma=86400The first block is a
103 Early Hintsresponse. curl prints every response it received, so a stack that sends hints produces two status lines for one request. - Step 2.
Keep the headers and the body in one stream when you need to read both.
curl -si https://example.com/ | head -20HTTP/2 200 date: Fri, 11 Sep 2026 18:58:54 GMT content-type: text/html server: cloudflare last-modified: Thu, 10 Sep 2026 21:00:47 GMT allow: GET, HEAD accept-ranges: bytes age: 6543 cf-cache-status: HIT cf-ray: a398e8155e550202-WAW <!doctype html><html lang="en"><head><title>Example Domain</title>… - Step 3.
Narrow the GET to the headers under test.
curl -s -D - -o /dev/null https://www.cloudflare.com/ | grep -i -E '^(cache-control|cf-cache-status|age|last-modified|accept-ranges)'cf-cache-status: HIT accept-ranges: bytes age: 1 cache-control: max-age=10 last-modified: Fri, 11 Sep 2026 19:00:40 GMT - Step 4.
Run the same filter through
-Iand compare the two lists.curl -sI https://www.cloudflare.com/ | grep -i -E '^(cache-control|cf-cache-status|age|last-modified|accept-ranges)'cache-control: no-storeOne line instead of five, and the caching rule is inverted.
-Isends HEAD, and this origin answers HEAD from a different code path. Reading-Ioutput here would report the page as uncacheable. - Step 5.
Send
-Ito a route that accepts POST only.curl -sI https://httpbin.org/postHTTP/2 405 date: Fri, 11 Sep 2026 19:04:00 GMT content-type: text/html content-length: 178 server: gunicorn/19.9.0 allow: POST, OPTIONS access-control-allow-origin: * access-control-allow-credentials: trueThe route works. HEAD is what it rejects, and
allownames the methods it takes. - Step 6.
Match header names without case, because HTTP/2 sends them lowercase.
curl -s -D - -o /dev/null https://example.com/ | grep -i 'content-type'content-type: text/htmlThe same pipeline ending in
grep -c 'Content-Type'returned0on this host.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| Two status lines for one request | The server sent 103 Early Hints before the real response | Read the second block. Assertions belong on the final status line. |
| cf-cache-status: HIT with age: 1 | A CDN served a copy that is 1 second old | Confirm the page is safe to cache for everyone before accepting a HIT. |
| server: cloudflare or server: gunicorn/19.9.0 | The stack names itself and its version to anyone | Version strings tell an attacker which advisories to try. Trim the header at the proxy. |
| allow: POST, OPTIONS next to a 405 | The method was refused, not the route | Repeat with the method the header names before reporting the endpoint down. |
| A header you expect is absent | It is absent on this path, not necessarily on the site | Request the exact path the client uses, then the origin directly, to find where it is dropped. |
Common mistakes
What to check next
- How to check HTTP status code: the status line at the top of every dump above, on its own.
- How to test API with curl: add a method, a body and a token to the request these headers answer.
- How to check API endpoint: the scripted version of the same read.
- How to test CORS with curl: the header set changes once the request carries an
Origin. - What to check before launching a website: the header pass before a release.
FAQ
How to view HTTP headers in Chrome?
Open DevTools, Network tab, reload the page, click the request, then open the Headers panel. Response Headers is the same list curl prints. Click "Raw" to see the values unformatted, which matters for cookies and long CSP strings that Chrome wraps.
How to check the server header of a site?
curl -sI https://example.com/ | grep -i '^server:'. The value is optional, and a version inside it, like gunicorn/19.9.0 in step 5, tells anyone which known issues apply to that build. Removing the version at the proxy costs nothing.
What is the difference between -i and -I?
-i adds the headers to the body of the request you were already making. -I replaces the request with a HEAD. -i is safe on any route. -I is a different request and can return different headers or a different status.
Can curl show the request headers as well?
Yes. curl -v prints the request headers prefixed with > and the response headers with <. Pipe it through grep -E '^[<>]' to drop the TLS handshake lines and keep only the exchange.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic4 minpublished updated Maks Verny