curl -i vs curl -I vs curl -v: The Three Flags That Confuse Everyone
If you\’ve ever searched \”how do I get just the headers from curl?\” you\’ve
seen this mess:
- One Stack Overflow answer says
curl -I - Another says
curl -i - A third says
curl -v - A fourth recommends
curl -X HEAD -i(which is broken)
They all do different things. This article sorts them out using curl
8.21.1 (current as of July 2026), the official man page, and Daniel
Stenberg\’s (curl\’s creator) own Stack Overflow answer.
TL;DR — The One-Second Answer
| Flag | Long form | What you get | HTTP method |
|---|---|---|---|
-i |
--show-headers |
Body + headers in one stream | GET |
-I |
--head |
Headers only, no body | HEAD |
-v |
--verbose |
Request line + headers + TLS + timing, all to stderr | GET |
If you want only the headers: use -I. If you want headers plus the
body: use -i. If you\’re debugging a connection problem: use -v.
The Detailed Comparison
-i / --show-headers: include headers in the output
This makes curl prepend the response headers to the regular output stream.
The body still downloads — it\’s just that you see the headers first.
$ curl -i https://example.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
Date: Tue, 14 Jul 2026 12:00:00 GMT
Server: ECS (sec/9743)
Accept-Ranges: bytes
Age: 423741
Cache-Control: max-age=604800
Etag: \"3147526947+gzip\"
Expires: Tue, 21 Jul 2026 12:00:00 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Vary: Accept-Encoding
X-Cache: HIT
<!doctype html>
<html>
<head>...</head>
<body>
... full body here ...
</body>
</html>You get everything: headers, then body. Useful when debugging a single
request and you want to see both the metadata and the actual content in
one stream.
Heads-up: in curl 8.10.0 (released November 2024), the long form was
renamed from --include to --show-headers. The old --include still
works but is deprecated:
# Both work, but the second is the canonical name in 2026
curl -i https://example.com
curl --show-headers https://example.com-I / --head: fetch headers only
This sends a real HTTP HEAD request — the server responds with only
the headers, no body. Faster, less bandwidth, and the right tool for
probing a URL\’s status without downloading anything.
$ curl -I https://example.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
Date: Tue, 14 Jul 2026 12:00:00 GMT
...Same output as -i but without the body. The connection closes as
soon as headers arrive.
Bonus: on ftp:// and file:// URLs, -I displays just the file
size and last-modified time, which is a handy one-liner for shell scripts:
$ curl -I https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso
HTTP/1.1 200 OK
Content-Length: 5959565824
Last-Modified: Thu, 25 Apr 2024 18:42:31 GMT
...That\’s the iso size in bytes and when it was published — useful for
checksumming or \”did this file change since last week?\” scripts.
-v / --verbose: show the full conversation
-v is for debugging, not for parsing. It prints to stderr:
*— info messages (DNS lookup, TCP connect, TLS handshake, etc.)>— bytes curl sent (request line, headers, body)<— bytes curl received (response line, headers, body)
$ curl -v https://example.com 2>&1 | head -20
* Trying 93.184.216.34:443...
* Connected to example.com (93.184.216.34) port 443
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, FINISHED (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, FINISHED (20):
* SSL connection using TLS_AES_128_GCM_SHA256
* Server certificate:
* subject: CN=example.com
* start date: Jun 3 00:00:00 2026 GMT
* expire date: Jun 3 23:59:59 2027 GMT
* issuer: C=US; O=DigiCert Inc; CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1
...
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.21.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=UTF-8
...You can crank the verbosity:
-vv— adds timestamps and trace IDs-vvv— also dumps the actual transfer content (full request and
response bodies)-vvvv— also enables tracing for all network components
Privacy warning from the man page: verbose output can include
usernames, credentials, bearer tokens, and secret payload content. Be
careful before pasting -v output into a chat or GitHub issue.
When to Use Each
Use -I when…
You want to probe a URL without downloading the body. Common cases:
# Is the site up?
curl -I https://example.com | head -1
# HTTP/1.1 200 OK
# Where does a short URL redirect to?
curl -IL https://bit.ly/example
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/full-path
# HTTP/1.1 200 OK
# What size is this download?
curl -I https://releases.example.com/big-file.tar.gz | grep -i content-length
# What type of file is at this URL?
curl -I https://example.com/mystery-file | grep -i content-typeUse -i when…
You want to see the headers of one specific request, alongside the
body — typically when debugging an API call or a single download:
# Debug a JSON API: see headers + parsed body
curl -i https://api.github.com/repos/torvalds/linux | head -30
# Check Set-Cookie headers while debugging auth
curl -i -c cookies.txt https://example.com/login
# Inspect Content-Encoding to verify gzip is on
curl -i -H \"Accept-Encoding: gzip\" https://example.com/big-fileUse -v when…
You\’re debugging a connection problem — TLS errors, timeouts,
unexpected redirects, DNS issues, proxy problems:
# Why is this SSL handshake failing?
curl -v https://expired.badssl.com/ 2>&1 | grep -A 3 \"TLS|certificate\"
# Which proxy is being used?
curl -v https://example.com 2>&1 | grep -i \"proxy|connect\"
# Trace the DNS resolution path
curl -v https://example.com 2>&1 | grep -A 2 \"Trying|Connected\"The -X HEAD -i Antipattern
This is a popular Stack Overflow answer that\’s been wrong since 2010 and
still circulates today:
# DON\'T DO THIS
curl -X HEAD -i https://example.comThe intent is \”send HEAD, also show headers.\” But:
-X HEADoverrides the default GET method to HEAD-iaddsshow headersto the response output- A real HEAD response has no body, but
-itells curl to wait
for Content-Length bytes anyway - The result: curl hangs for the duration of
timeoutwaiting for body
bytes that will never come
The correct alternative is just curl -I — same effect, no hang.
Saving Headers Separately with -D
If you want to pipe the body to another command but capture the headers
in a file:
# Save headers to headers.txt, pipe body to jq
curl -s -D headers.txt https://api.github.com/repos/torvalds/linux | jq .name
# Inspect the saved headers later
cat headers.txt-D (or --dump-header) writes headers to a file, body to stdout
separately. Useful for API workflows where you need the ETag or
X-RateLimit-Remaining after the fact.
Following Redirects
When a server returns 301 or 302, curl by default does not follow
the redirect. You see only the first response:
$ curl -I https://github.com/torvalds
HTTP/1.1 301 Moved Permanently
Location: https://github.com/torvalds/linuxAdd -L to follow:
$ curl -IL https://github.com/torvalds
HTTP/1.1 301 Moved Permanently
Location: https://github.com/torvalds/linux
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...You\’ll see the headers from every redirect hop. Combine with
-o /dev/null to suppress the body if you only want the final status:
# Final HTTP status of a URL after redirects
curl -IL -o /dev/null -w \"%{http_code}n\" https://github.com/torvalds
# 200Common Gotchas
Some servers don\’t implement HEAD
A small percentage of servers ignore HEAD requests or respond with a
405 Method Not Allowed. For those:
# Fall back to -i + suppress body if -I fails
curl -I https://example.com || curl -is https://example.com | head -10(-s silences the progress meter; pipe through head -10 to chop off
the body.)
CDNs may serve different content on HEAD vs GET
Cloudflare, Fastly, and CloudFront sometimes cache HEAD responses
differently than GET responses. If you see X-Cache: MISS on -I but
X-Cache: HIT on -i, the cache treats them as different cache keys.
-i with -o still prints headers to stdout
If you save the body to a file with -o, the headers still go to
stdout (not the file). To suppress them, use -D headers.txt to save
to a file, then --silent to silence stdout:
# Body to file, headers to separate file, no stdout noise
curl -s -D headers.txt -o body.html https://example.com-I and -X POST are incompatible
If you accidentally combine them, curl errors out:
$ curl -X POST -I https://example.com
curl: (3) HTTP: flags used together are mutually exclusive-I enforces HEAD as the method, so you can\’t override it.
Going Deeper: --trace and --trace-ascii
For debugging beyond what -v shows, use --trace or --trace-ascii.
These dump every byte in and out to a file, including the TLS-encrypted
stream:
curl --trace-ascii trace.txt https://example.com
# Then inspect trace.txt — full request, full response, all timingFor tracing specific components (TLS only, network only), use
--trace-config:
# Only trace the TLS handshake
curl -v --trace-config tls https://example.com 2>&1 | head -30Quick Reference Card
| Task | Command |
|---|---|
| Get headers only | curl -I https://example.com |
| Get headers + body (stdout) | curl -i https://example.com |
| Save headers to file | curl -D headers.txt https://example.com |
| Save body to file, headers to stdout | curl -i -o body.html https://example.com |
| Follow all redirects, show all headers | curl -IL https://example.com |
| Final status code after redirects | curl -L -o /dev/null -w \"%{http_code}n\" https://example.com |
| Full request/response debug | curl -v https://example.com 2>&1` |
| TLS-only debug | curl -v --trace-config tls https://example.com |
| API call with rate-limit inspection | curl -s -D - https://api.example.com/data |
| File size + last-modified | curl -I https://example.com/file |
Comments