In this episode we'll dissect failed requests: reading verbose and full trace logs, analyzing the timing of each connection phase, translating curl exit codes, and mapping solutions for DNS errors, refused connections, timeouts, SSL certificates, and proxies.

In episode 17, you saw how curl becomes the backbone of many API testing tools and the automation ecosystem — a tool that seems "invisible" until a request suddenly fails on a production server. That's when debugging becomes the difference between a practitioner who guesses and a practitioner who reads evidence.
The problem is, curl is quiet. When a request fails, it just exits with a numeric code and maybe one error line — without explaining where exactly it happened. Like a car stalling mid-road with no indicator lights: you know something's wrong, but not which system. Episode 18 installs a "full dashboard" on curl: verbose logs, byte-by-byte traces, timing measurements for each phase, and a dictionary of the exit codes and errors most seen in the real world.
-v: Seeing the Process from InsideThe -v (verbose) option makes curl open the machine's door and narrate every step it takes. This is the first debugging option you should switch on whenever something feels off.
curl -v https://api.example.com/healthThe output is long, but the information is structured. Here's an example of its shape:
* Trying 104.18.20.123:443...
* Connected to api.example.com (104.18.20.123) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
> GET /health HTTP/2
> Host: api.example.com
> User-Agent: curl/8.x.x
> Accept: */*
>
< HTTP/2 200
< content-type: application/json
<
{"status":"ok"}The key to reading it is in the three line prefixes:
* are curl's internal information: the address connected to, TLS phases, up to DNS resolution.> are the request curl sends to the server.< are the response curl receives from the server.With this pattern, you immediately know the failure point: if there's no * Connected line, the TCP connection itself failed. If the TLS lines don't appear, the problem is in the handshake. If the > headers were sent but a < response never comes, it's most likely the server at fault — not the network.
Tip
When using -v, add -sS to mute the progress meter but keep errors visible: curl -sS -v https://api.example.com/health. The output stays clean on screen without scrolling progress lines, without dropping important error messages.
--trace and --trace-ascii-v shows a summary, but sometimes you need a complete recording — like an airplane's black box recording the entire conversation. The --trace-ascii option stores all data sent and received, including header contents, body, and handshake details not visible in -v.
curl --trace-ascii trace.txt https://api.example.com/healthThe file version is very useful for long requests — the result can be opened in an editor and searched for suspicious lines. The - prefix on the argument means "write to the terminal". If you need to see raw bytes, including full hexadecimal data, use --trace — this version writes all data in hexadecimal format, useful when debugging encoding or low-level protocol issues.
-wClear errors do help, but the hardest cases are usually about performance: the request succeeds, but is slow. That's when you need a stopwatch. The -w (write-out) option you met in episode 6 for printing status codes — now we use it to measure each connection phase.
curl -s -o /dev/null -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nByte pertama: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
https://api.example.com/health-o /dev/null discards the body so what's measured is pure time, not terminal-render time. Each variable measures a different phase:
| Variable | Phase measured |
|---|---|
time_namelookup | DNS resolution: turning a name into an IP address |
time_connect | TCP handshake: forming the connection |
time_appconnect | TLS handshake: encryption negotiation complete |
time_starttransfer | Time until the first response byte arrives |
time_total | Total for the entire transfer |
Notice the pattern: if time_connect is high, the problem is the network. If time_connect is low but time_appconnect is high, it's most likely TLS or CA. If everything is fast but time_starttransfer spikes, the server is slow to respond — or the connection hangs on the server side. Reading these numbers is like reading an EKG: each spike points to a different organ.
When curl finishes, it exits with an exit code. This number is the language scripts understand — not just "failed", but the failure category. Inside a script, you can check it explicitly:
curl -sS https://api.example.com/health
echo "exit code: $?"| Code | Meaning | Usually because |
|---|---|---|
| 0 | Success | - |
| 6 | Could not resolve host | DNS failed or a hostname typo |
| 7 | Failed to connect | Port closed, service down, or refused |
| 22 | HTTP page not retrieved | Status code >= 400 when using --fail |
| 28 | Operation timeout | Connection or transfer exceeded the time limit |
| 35 | SSL connect error | Failed during the TLS handshake |
| 60 | SSL certificate problem | Invalid certificate or untrusted CA |
| 77 | CA cert cannot be read | CA bundle file missing or corrupt |
Tip
Memorize the three most important codes: 6 (DNS), 7 (connection), and 28 (timeout) — these cover the majority of real-world network failures. For scripts, combine with --fail-with-body so curl also exits non-zero when the server returns a 4xx/5xx status — not only when the connection itself fails.
Now let's map the most frequent errors to their root causes. Remember the principle from episode 9: don't guess, look at the evidence.
Could not resolve hostThis error appears when curl can't turn the hostname into an IP address. The most common causes: a name typo, a DNS server that can't answer, or a broken connection to the DNS server itself.
dig +short api.example.comIf dig is empty or errors, the problem is DNS — not curl. Try another hostname, or change the system DNS server. If dig succeeds but curl still fails, check whether a proxy is interfering (see the proxy section below).
Connection refusedThis message means the server refused the TCP connection — usually because no service is listening on that port. It can also be an explicit firewall denial, or the service simply isn't active yet.
ss -tlnp | grep ':443'If there's no LISTEN line for port 443, the application isn't running — restart the service. If there is, check that curl is hitting the right host and port; a correct host with a wrong port is the most confusing cause.
A timeout means curl gave up waiting for an answer. Unlike Connection refused (the server refuses quickly), a timeout happens when packets are left hanging — usually because a firewall silently drops them (silent drop) or the server is overwhelmed. Don't immediately raise the time limit without evidence; measure first with -w to see which phase the bottleneck occurs in.
SSL certificate problemThis error appears when TLS verification fails: expired certificate, hostname mismatch, or a CA chain the system doesn't recognize. The debugging isn't disabling verification, but inspecting the server certificate:
echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
| openssl x509 -noout -datesThe notBefore and notAfter lines show the certificate's validity period. If it's past notAfter, that's why verification fails — and the solution is on the server owner's side, not yours.
Warning
Never solve an SSL certificate problem with the -k or --insecure flag — that disables certificate verification and makes the connection interceptable by anyone on the path. The correct way is to fix the cause: update the CA bundle, fix the system clock, or contact the server owner. We'll discuss proper TLS policy thoroughly in episode 21.
The sneakiest errors come from an interfering proxy. The symptom: curl fails in a particular environment (CI, office, VPN) even though it works on your laptop. curl reads environment variables like http_proxy, https_proxy, and NO_PROXY automatically. If those variables are misconfigured, all your requests slam into a proxy that doesn't exist.
env | grep -i proxycurl --noproxy '*' https://api.example.com/healthIf --noproxy '*' works, your environment is injecting a proxy. Check the contents of http_proxy and https_proxy, then fix them or set NO_PROXY for internal hosts. Conversely, if a request actually needs to go through a proxy, use -x explicitly — and note that Could not resolve proxy means the proxy hostname itself isn't resolving, a DNS problem again.
The key to curl debugging is building a consistent ladder, from the fastest to the deepest:
-v — which phase does the log stop at?-w — quantify each phase's timing.--trace-ascii — open the byte-by-byte details.This order prevents wasted time: if it fails at DNS, don't open certificates. If it fails at TLS, don't fiddle with proxies. Each log layer gives a clue to the next layer.
Episode 18 equips you with a complete diagnostic kit: reading verbose logs with the *, >, and < prefixes, recording full traces with --trace-ascii, measuring each connection phase's timing with -w, translating curl exit codes from DNS to certificates, and mapping common errors to their root causes.
The most valuable thing isn't the tools, but the habit: debugging isn't guessing, but reading evidence in sequence. Start from the exit code, descend to verbose, quantify with timing, and open the trace if needed.
In the next episode 19, we'll move from repair mode to exploitation mode: the latest stable curl 8.x features — from --json and --oauth2-bearer, WebSocket and HTTP/3, to parallel transfers and --libcurl. See you!