curl: HTTP Requests, Headers, Auth, and Debugging TLS

1 min readSystems & Networking

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.

shellhttptlssecurity

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 / Security

curl'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?

easy

The 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?