curl: HTTP Requests, Headers, Auth, and Debugging TLS
curl sends HTTP requests from the command line. Key flags: -X for method, -H for headers, -d for request body, -o for output file, -v for verbose (shows request/response headers and TLS handshake), -k to skip certificate verification. curl is essential for testing APIs, debugging proxies, and understanding HTTP without a browser.
Common curl patterns
# GET request (default)
curl https://api.example.com/users
# POST with JSON body
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "alice", "email": "[email protected]"}'
# POST with form data
curl -X POST https://api.example.com/login \
-d "username=alice&password=secret"
# Add headers (auth token, custom header)
curl https://api.example.com/protected \
-H "Authorization: Bearer eyJ..."
# Save response to file
curl -o response.json https://api.example.com/data
# Follow redirects
curl -L https://short.url/abc
# Show response headers only
curl -I https://example.com
# Verbose: show request headers, response headers, TLS handshake
curl -v https://api.example.com/users
Debugging TLS with -v
curl -v https://api.example.com/users
Output includes:
* Trying 203.0.113.10:443...
* Connected to api.example.com (203.0.113.10) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate:
* subject: CN=api.example.com
* start date: Jan 1 00:00:00 2024 GMT
* expire date: Jan 1 00:00:00 2025 GMT
* issuer: Let's Encrypt
> GET /users HTTP/1.1
> Host: api.example.com
> User-Agent: curl/8.1.2
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
{"users": [...]}
Lines starting with * are curl metadata. > lines are request headers sent. < lines are response headers received.
Certificate errors
# Certificate Verify Failed — expired, self-signed, or hostname mismatch
curl https://self-signed.example.com
# curl: (60) SSL certificate problem: self-signed certificate
# Skip verification (INSECURE — only for testing internal services)
curl -k https://self-signed.example.com
# Use a custom CA bundle
curl --cacert /path/to/ca-cert.pem https://internal.example.com
# Specify client certificate for mutual TLS
curl --cert client.pem --key client.key https://mtls.example.com
-k skips certificate verification — never use it in scripts that run against production
GotchaNetworking / Securitycurl's -k flag disables all certificate checks: expired certs, self-signed certs, hostname mismatches, and revoked certs. In a test environment testing a local service with a self-signed cert, -k is convenient. In a script that runs against a production API, -k makes every TLS error invisible — including man-in-the-middle attacks where a proxy presents a fake certificate. Use --cacert to add a custom CA certificate instead of disabling verification entirely.
Prerequisites
- TLS/SSL
- Certificate authorities
- HTTPS
Key Points
- -k / --insecure: skip all certificate validation. Never in production scripts.
- --cacert: add a custom CA certificate (for internal CAs or self-signed in controlled environments).
- -v shows the full TLS handshake and certificate chain for debugging.
- --resolve: override DNS for a specific host (test with production cert against a staging server).
Useful debugging flags
# Time each phase of the request
curl -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" \
-o /dev/null -s https://api.example.com/users
# Send request with specific TLS version
curl --tlsv1.3 https://api.example.com/users
# Override DNS for hostname (test staging with prod cert)
curl --resolve api.example.com:443:192.168.1.100 https://api.example.com/users
# Upload file
curl -F "file=@/path/to/file.pdf" https://api.example.com/upload
# Set timeout
curl --connect-timeout 5 --max-time 30 https://api.example.com/slow
The -w format string with -o /dev/null -s (suppress body, silent mode) shows timing for each connection phase — useful for diagnosing whether latency is in DNS, TCP connect, TLS handshake, or server response.
curl returns 'SSL certificate problem: certificate has expired'. The API works fine in the browser. Why might the browser succeed where curl fails?
easyThe certificate is expired. The browser has a setting to allow expired certificates with a warning. The curl command has no -k flag.
Acurl is stricter about expired certificates than browsers
Incorrect.This is true in practice — browsers often let users proceed past expired cert warnings, but it's not that curl is 'stricter'. The fundamental behavior is: expired cert = TLS error. Browsers present a user warning and allow override; curl returns an error by default.BThe browser shows a security warning for the expired cert but the user clicked 'proceed anyway'. curl fails hard on any certificate error unless -k is passed.
Correct!Browsers present an 'advanced → proceed anyway' option for certificate errors (expired, self-signed, hostname mismatch). The user accepted the risk and connected. curl has no interactive mode — it fails immediately on certificate errors unless -k is passed to skip verification. In automated scripts, this is the correct behavior: a script should not silently proceed past certificate errors. Fix: renew the certificate, or add -k for a temporary workaround with full understanding of the security implications.CThe browser uses a different CA bundle than curl
Incorrect.While possible (curl uses the system CA bundle or its own compiled-in bundle), an expired certificate would fail in both places. Expiry is checked independently of trust chains.DThe browser caches the previous successful TLS session
Incorrect.TLS session resumption caches session keys, not certificates. An expired certificate is re-checked on every new connection and session resumption attempt.
Hint:How do browsers handle certificate errors interactively compared to curl's automated behavior?