Reading curl -v: what the arrows and asterisks mean

29/10/2026 · Laurent Monitoring practice
Reading curl -v: what the arrows and asterisks mean

curl -v https://example.com/ produces forty lines that most people scroll past looking for the status code. The forty lines are the point. They are a transcript of every step a browser takes, in order, with the failure sitting exactly where it happened.

Reading them requires knowing three characters.

PrefixMeans
*curl telling you what it is doing. Resolution, connection, TLS, certificate details, connection reuse.
>Sent to the server. Your request line and your headers.
<Received from the server. Its status line and its headers.

Everything with an asterisk happened on your machine or between the two machines. Everything with an arrow is the conversation itself. Separating those two is most of the skill.

The four blocks, and what a failure in each one means

A successful run always has the same shape.

Resolution and connection. Lines showing the address chosen and the port. If curl stops here, the problem is DNS or the network, and the specific wording matters: a name that does not resolve, a connection that is refused, and a connection that times out are three different faults with three different owners. A refusal eliminates far more than a timeout does.

The address curl prints is also the answer to half of all migration questions. If it is not the address you expect, nothing further in the output is about the server you were thinking of.

The TLS handshake. Protocol and cipher agreed, then the certificate chain, then the verification result. A failure here never reaches the web server at all, which is why the application logs will be empty and the client will insist nothing changed. What fails, in what order, and how to tell which step gave up is in the handshake, step by step.

The request. Your lines, prefixed with >. Worth reading when you are debugging something that works in a browser and not in a script, because the difference is usually visible right here: no Accept-Language, no cookie, a User-Agent that says curl, and a site that behaves differently for all three.

The response. The status line and the headers. This is where the interesting evidence usually is, and the headers are more informative than the code: the Server header naming a proxy you did not know was in front of the site, a CF-Cache-Status or an X-Cache revealing you are talking to a cache rather than the origin, a Location starting a redirect chain, a Set-Cookie that explains why the page is uncacheable, and a Strict-Transport-Security whose presence decides what visitors experience on the day a certificate lapses: the preload token in that header is easy to add and slow to undo.

The flag that is more useful than -v during an outage

-v tells you what happened. -w tells you how long each part took, which is what you need when the site is up and slow rather than down.

curl -sS -o /dev/null -w '
dns      %{time_namelookup}
connect  %{time_connect}
tls      %{time_appconnect}
ttfb     %{time_starttransfer}
total    %{time_total}
code     %{http_code}
ip       %{remote_ip}
' https://example.com/

The values are cumulative from the start of the request, so the cost of each stage is the difference between consecutive lines. Read it that way and the diagnosis is immediate: a large gap between connect and tls is a handshake problem, a large gap between tls and ttfb is the application thinking, and a large total with a small ttfb is a slow transfer rather than a slow server.

Save that as a shell function. It is the single most reused command in this trade, and typing it from memory during an incident is time you do not have.

It also replaces the reflex of pinging the host, which tests a protocol the site does not speak and answers cheerfully while the web server is stopped: what an echo reply proves, and what it does not.

Six invocations worth keeping

Test a specific server, ignoring DNS. The migration command.

curl -sSI --resolve example.com:443:203.0.113.10 https://example.com/

Follow redirects and see every hop.

curl -sSIL -o /dev/null -w '%{http_code} %{url_effective}\n' https://example.com/

Isolate a certificate problem. Adding -k skips verification. If the request then succeeds, the fault is the certificate and not the server. Use it to answer that one question and never in a script, because a script with -k in it has quietly stopped checking anything.

Rule out HTTP/2.

curl -sSI --http1.1 https://example.com/

Bypass an intermediate cache.

curl -sSI -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' https://example.com/

Make the HTTP status the exit status. By default curl exits successfully after fetching a 500, because from its point of view the transfer worked. In any scripted check, that default is a bug:

curl -fsS https://example.com/ > /dev/null || echo "failed"

Three ways the output misleads

-I is not a cheap GET. It sends a HEAD request, and a non-trivial number of applications handle HEAD on a different code path, or not at all. A site that answers 405 or 500 to HEAD and 200 to GET is not broken for visitors. When the result surprises you, re-run it as -sS -o /dev/null -D -, which performs a real GET and prints only the headers.

Connection reuse hides the handshake. Ask for several URLs in one invocation and curl reuses the connection, so only the first shows TLS lines. When you are timing the handshake specifically, one URL per invocation.

-v summarises the certificate. It shows the subject, the issuer and the validity dates, which is enough to spot an expiry and not enough for anything else. For the alternative names, the chain as actually served, and whether the intermediate is present, the tool is openssl, in three commands.

When even -v is not enough, because you suspect something about the bytes themselves, --trace-ascii - dumps the entire exchange including the body. It is unreadable in the way a packet capture is unreadable, and it is the right tool roughly once a year.

Turning one run into a check

Everything above is a person at a terminal, deciding what to look at. The same commands run unattended is a different discipline, because unattended means nobody is there to notice that the output looks odd. A scheduled curl needs an explicit expected status, an assertion on something in the body, and a timeout, or it will report success on a page that has been serving an error to visitors since Thursday: the layers a status code says nothing about.

Back to contents

Share this post.
Stay up-to-date

Subscribe to our newsletter

Don't miss this

You might also like