~/blog/-blog-debugging-with-curl-
blog · HTTP

Debugging APIs with cURL: The Commands Every Developer Needs

How to use cURL to diagnose API issues, inspect TLS certificates, test authentication headers, trace redirects, and reproduce exact browser requests from the command line.

last updated · June 14, 2026by @vultio

Why cURL is still the most reliable API debugging tool

GUI tools like Postman and Insomnia are useful for exploration, but cURL has two properties that make it irreplaceable for debugging: it is available on every server where you might need to diagnose a live issue, and it produces output that is copy-paste reproducible. A cURL command is an exact specification of an HTTP request — headers, method, body, TLS settings, timeout — that runs identically on any machine with cURL installed.

The debugging workflow most developers never fully exploit: copy the failing request from the browser's Network tab as cURL, run it from the terminal, then methodically strip parts of the request to isolate which header, cookie, or body field is causing the issue. What takes minutes to isolate in a GUI tool takes seconds with command-line cURL and basic flag manipulation.

The verbose flag: see everything

-v (verbose) is the single most useful cURL flag for debugging. It prints the full request headers cURL sends, the full response headers received, and TLS handshake information. When an API is misbehaving, run with -v first.

curl -v https://api.example.com/users/123

# Output explanation:
*   Trying 93.184.216.34:443...   # DNS resolved, connecting
* Connected to api.example.com (93.184.216.34) port 443
* TLS handshake info...
> GET /users/123 HTTP/2           # > lines = request headers sent
> Host: api.example.com
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 200                      # < lines = response headers received
< content-type: application/json
< x-request-id: req_abc123
<
{"id":"123","name":"Alice"}       # response body

# To separate headers from body in a file:
curl -v https://api.example.com/users/123 2>headers.txt | jq .

Inspecting TLS issues

TLS certificate errors are a common source of mysterious API failures, especially in environments with corporate proxies, self-signed certificates, or certificate pinning. cURL gives you fine-grained control over TLS verification.

# See full TLS certificate information
curl -v https://api.example.com 2>&1 | grep -A 20 'Server certificate'

# Check certificate expiry (useful in monitoring scripts)
curl -vI https://api.example.com 2>&1 | grep 'expire date'

# Skip TLS verification (ONLY for debugging, never in production)
curl -k https://api.example.com/users

# Use a specific CA certificate bundle (corporate proxy / internal CA)
curl --cacert /path/to/company-ca.crt https://internal.example.com

# Show SSL/TLS connection details
curl --ssl-revoke-best-effort -v https://api.example.com 2>&1 | grep -i ssl

# Test with a specific TLS version
curl --tlsv1.3 https://api.example.com

Reproducing browser requests exactly

The fastest way to get a working cURL command for any browser request: open DevTools, go to the Network tab, right-click the request, and select "Copy as cURL". This produces a cURL command with every header the browser sent — including cookies, session tokens, and security headers. Paste it into a terminal and it will behave exactly like the browser request.

Once you have the exact command, use binary search to find the problematic part: remove half the headers, run again, see if it still fails. This isolates which header (or combination of headers) is causing the issue in roughly log₂(N) tries, where N is the number of headers.

# Typical browser-copied cURL command (from Chrome/Firefox "Copy as cURL")
curl 'https://api.example.com/dashboard'   -H 'authority: api.example.com'   -H 'accept: application/json, text/plain, */*'   -H 'accept-language: en-US,en;q=0.9'   -H 'authorization: Bearer eyJhbGc...'   -H 'content-type: application/json'   -H 'cookie: session=abc123; csrf=xyz'   -H 'origin: https://app.example.com'   -H 'referer: https://app.example.com/dashboard'   -H 'user-agent: Mozilla/5.0 ...'   --compressed

# Minimal version — strip to essentials to isolate the issue:
curl 'https://api.example.com/dashboard'   -H 'Authorization: Bearer eyJhbGc...'

Testing authentication flows

# Bearer token (JWT or opaque)
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."   https://api.example.com/users/me

# API key in header
curl -H "X-API-Key: sk_live_abc123" https://api.example.com/data

# Basic auth (username:password, base64-encoded automatically by curl)
curl -u username:password https://api.example.com/protected

# OAuth2 — get token first, then use it
TOKEN=$(curl -s -X POST https://auth.example.com/token   -d "grant_type=client_credentials"   -d "client_id=myapp"   -d "client_secret=mysecret" | jq -r '.access_token')

curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data

# Session cookie authentication
curl -c cookies.txt -b cookies.txt   -X POST https://example.com/login   -d "email=user@example.com&password=pass"
# cookies.txt now has session cookie
curl -b cookies.txt https://example.com/protected

Diagnosing redirects and timeouts

# Follow redirects and show each step
curl -L -v https://example.com/old-url

# Show only the final URL after all redirects
curl -Ls -o /dev/null -w "%{url_effective}" https://example.com/short-link

# Check response time breakdown
curl -o /dev/null -s -w "
DNS:        %{time_namelookup}s
Connect:    %{time_connect}s
TLS:        %{time_appconnect}s
First byte: %{time_starttransfer}s
Total:      %{time_total}s
HTTP Code:  %{http_code}
" https://api.example.com/users

# Set timeouts (important for diagnosing hung connections)
curl --connect-timeout 5    # max 5s to establish connection
     --max-time 30           # max 30s total request time
     https://api.example.com/slow-endpoint

# Check if an API is up without downloading the body
curl -I https://api.example.com/health    # HEAD request
curl -o /dev/null -s -w "%{http_code}" https://api.example.com/health

Pretty-printing JSON responses

API responses are often minified. Pipe cURL output through jq to format and filter JSON on the fly. jq is the most useful companion tool for API debugging with cURL.

# Pretty print
curl -s https://api.example.com/users/123 | jq .

# Extract a specific field
curl -s https://api.example.com/users/123 | jq '.email'

# Extract from an array
curl -s https://api.example.com/orders | jq '.[].id'

# Filter and transform
curl -s https://api.example.com/users   | jq '[.[] | select(.status == "active") | {id, email}]'

# Test response and set exit code (useful in CI)
curl -sf https://api.example.com/health   | jq -e '.status == "ok"'
# exits with 1 if condition is false — usable in shell if statements

The -s flag silences cURL's progress output, which would otherwise interfere with piping to jq. Use -sf (-f fails silently on HTTP errors) for scripts where a non-200 response should trigger a non-zero exit code.