
## What it is

curl is the universal HTTP client for the command line. In a deployment context it's the glue between every other tool: triggering deploy webhooks, smoke-testing endpoints after a release, looping on healthchecks, asserting TLS renewals, and measuring response timing for SLO checks. Most of what makes curl deploy-grade isn't the request syntax — it's the exit-code discipline (`--fail`), the timeouts (`--max-time`), and the retry policy (`--retry-all-errors`) that decides whether your CI job correctly fails on a half-broken release.

This cheatsheet skips the trivia and focuses on the flags you reach for during a deploy session.

## Quick reference

### Basic requests

```bash
curl https://api.example.com/health             # GET (default)
curl -I https://api.example.com/health          # HEAD only — fast healthcheck
curl -X POST https://api.example.com/builds     # explicit method
curl -X DELETE https://api.example.com/builds/42

# Follow redirects (POST stays POST)
curl -L --post301 --post302 -X POST https://api.example.com/deploys
```

### Headers and auth

```bash
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me
curl -H "X-Deploy-Signature: $SIG"     https://hooks.example.com/deploy
curl -u user:pass                      https://api.example.com/private    # basic auth
curl --user-agent "deploy-bot/1.0"     https://api.example.com/build
curl -A "deploy-bot/1.0"               https://api.example.com/build      # alias for --user-agent
```

### Output and inspection

```bash
curl -s   https://api.example.com         # silent (no progress bar)
curl -sS  https://api.example.com         # silent BUT show errors — use this in scripts
curl -i   https://api.example.com         # include response headers in body
curl -I   https://api.example.com         # ONLY response headers (HEAD)
curl -v   https://api.example.com         # verbose — TLS handshake + headers + body
curl -o response.json    https://...      # write body to file
curl -O                  https://.../file.tar.gz   # save with remote filename
curl -w "%{http_code} %{time_total}\n" -o /dev/null -s https://api.example.com
```

### JSON, form data, file uploads

```bash
# JSON (curl 7.82+) — sets Content-Type and Accept automatically
curl --json '{"tag":"v1.4.2"}' https://api.example.com/deploys

# JSON the manual way
curl -H "Content-Type: application/json" -d '{"tag":"v1.4.2"}' https://api.example.com/deploys

# JSON from file
curl --json @payload.json https://api.example.com/deploys

# URL-encoded form data
curl -d "key=value" -d "tag=v1.4.2" https://hooks.example.com/deploy
curl --data-urlencode "comment=Released $(git rev-parse --short HEAD)" https://...

# Multipart upload
curl -F "file=@release.tar.gz" -F "sha=$SHA" https://api.example.com/artifacts

# Raw upload (PUT a file)
curl -T release.tar.gz https://api.example.com/artifacts/release.tar.gz
```

### Timing, retries, timeouts

```bash
curl --connect-timeout 5 --max-time 30 https://api.example.com   # never hang
curl --retry 3 --retry-delay 2 https://flaky.example.com         # retry transient errors
curl --retry 3 --retry-all-errors https://flaky.example.com      # retry on ALL failures (curl 7.71+)
curl --retry 5 --retry-max-time 60 https://flaky.example.com     # cap total retry window

# Detailed timing (great for SLO assertions)
curl -o /dev/null -s -w "DNS:%{time_namelookup} CONN:%{time_connect} TLS:%{time_appconnect} TTFB:%{time_starttransfer} TOTAL:%{time_total}\n" https://api.example.com
```

### TLS, certs, cookies

```bash
curl --cacert ca.pem      https://...           # trust a custom CA
curl --cert client.pem --key client.key https://mtls.example.com   # mTLS
curl --tlsv1.3            https://...           # force TLS 1.3 minimum
curl --resolve api.example.com:443:10.0.0.5 https://api.example.com  # DNS override

# Cookies
curl -c cookies.txt https://login.example.com/auth   # save cookies
curl -b cookies.txt https://api.example.com/me       # send cookies
```

`-k` / `--insecure` skips TLS verification. Don't use it in production deploys — fix the cert chain instead.


## Deployment workflows (the moat)

### 1. Trigger a DeployHQ deploy via webhook

Most CI providers can hit a webhook to kick off a DeployHQ deploy after the build completes. Pattern:

```bash
#!/usr/bin/env bash
set -euo pipefail

WEBHOOK_URL="${DEPLOYHQ_WEBHOOK_URL:?missing}"
SHA="$(git rev-parse HEAD)"
BRANCH="$(git rev-parse --abbrev-ref HEAD)"

curl -fsS \
  --max-time 30 \
  --retry 3 --retry-delay 5 --retry-all-errors \
  -H "Content-Type: application/json" \
  --data-urlencode "sha=${SHA}" \
  --data-urlencode "branch=${BRANCH}" \
  "$WEBHOOK_URL"
```

Three deploy-grade flags carrying the weight:

- `-f` / `--fail` — exit non-zero on HTTP ≥ 400 (without it, curl returns 0 even on a 500)
- `--max-time 30` — never hang the CI runner
- `--retry-all-errors` — retry through transient network blips, not just curl's own errors

[DeployHQ's automatic deployments](https://www.deployhq.com/features/automatic-deployments) accept this exact webhook shape.

### 2. Smoke-test the release after deploy

The most common CI bug: tests "pass" because curl returns 0 even when the staging URL serves an HTTP 502. Fix:

```bash
curl --fail-with-body \
     --max-time 10 \
     --retry 3 --retry-delay 5 --retry-all-errors \
     "https://staging.example.com/healthz"
```

`--fail-with-body` (curl 7.76+) is the version of `--fail` that prints the response body on error — so you actually get the panic message, not a silent exit code 22. Make this the first step after the DeployHQ webhook completes — if the new release isn't healthy, fail loudly.

### 3. Wait-for-200 healthcheck loop

Right after a zero-downtime swap, the new instances may take a few seconds to register. Wait for them rather than racing the next step:

```bash
#!/usr/bin/env bash
set -euo pipefail
URL="${1:?healthcheck URL required}"
DEADLINE=$((SECONDS + 120))

until curl -fsS --max-time 5 -o /dev/null "$URL"; do
  [[ $SECONDS -lt $DEADLINE ]] || { echo "Healthcheck timeout: $URL" >&2; exit 1; }
  sleep 2
done
echo "Healthy: $URL"
```

Pair with [DeployHQ's zero-downtime deployments](https://www.deployhq.com/features/zero-downtime-deployments) — the swap is atomic, but DNS and load-balancer registration aren't. The loop closes that gap.

### 4. Assert response timing (SLO smoke check)

If your post-deploy SLO is "TTFB under 500ms from a fresh container", encode it:

```bash
TTFB_MS=$(curl -o /dev/null -sS \
  -w "%{time_starttransfer}" \
  --max-time 5 \
  "https://api.example.com/healthz" \
  | awk '{printf "%d", $1 * 1000}')

if (( TTFB_MS > 500 )); then
  echo "TTFB regression: ${TTFB_MS}ms (budget 500ms)" >&2
  exit 1
fi
```

Drop this into [a build pipeline step](https://www.deployhq.com/features/build-pipelines) and the deploy fails the moment a release crosses the SLO budget.

### 5. Verify TLS chain after a cert renewal

After Let's Encrypt rotates a cert, confirm the served chain matches expectations before the next deploy:

```bash
curl -vI --max-time 10 https://example.com 2>&1 \
  | grep -E "expire date|subject|issuer|TLSv1\.[23]"
```

For a hard assertion (bail if cert expires within 30 days):

```bash
END=$(curl -vI https://example.com 2>&1 | sed -n 's/.*expire date: //p')
[[ -n "$END" ]] || { echo "no expiry parsed" >&2; exit 1; }
END_TS=$(date -d "$END" +%s)
NOW_TS=$(date +%s)
DAYS=$(( (END_TS - NOW_TS) / 86400 ))
(( DAYS > 30 )) || { echo "Cert expires in $DAYS days" >&2; exit 1; }
```

### 6. Verify a webhook signature you just received

When DeployHQ (or any provider) signs outbound webhooks with HMAC-SHA256, the verification side uses curl-adjacent shell tooling rather than curl itself, but the receiver script is part of every deploy hook:

```bash
# Inside your webhook receiver
EXPECTED=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | base64)
[[ "$EXPECTED" == "${HTTP_X_HUB_SIGNATURE_256#sha256=}" ]] \
  || { echo "Invalid signature" >&2; exit 1; }
```

The `--fail` discipline applies on the *sending* side — when curl POSTs the webhook, exit non-zero on a 4xx/5xx so the sender retries cleanly. See [the agent-driven CI/CD walkthrough](https://www.deployhq.com/blog/ai-agents-cicd-pipelines-github-issue-to-production-deploy) for the full webhook-sender + receiver pattern in a real deploy pipeline.


## Common errors and fixes

| Symptom | Fix |
|---|---|
| curl returns exit code 0 on HTTP 500 | Use `-f` / `--fail` (or `--fail-with-body` for the response body too) |
| Auth header is dropped after a redirect | `--location-trusted` *only* if you understand the cross-host implications; otherwise restructure to avoid the redirect |
| POST body is dropped on 301/302 | Add `--post301 --post302` (or use 307/308 redirects on the server) |
| `curl: (60) SSL certificate problem` | Fix the chain or use `--cacert /path/to/ca.pem` — never `-k` in production |
| Stuck waiting for slow upstream | Always set both `--connect-timeout` and `--max-time` |
| JSON body fails with double-quote escaping | Use `--json` (curl 7.82+) or `--data @file.json` |
| `curl: (6) Could not resolve host` in CI | DNS-over-TCP at runner start — add `--retry 3 --retry-all-errors` to absorb the cold start |
| Output garbled in CI logs | `-sS` (silent, but show errors) instead of `-s` |
| Progress bar in script output | `-s` or `--no-progress-meter` |
| `curl --json` not recognized | Pre-7.82 curl — use `-H "Content-Type: application/json" -d @body.json` |


## Companion: full DeployHQ deploy pipeline

curl is the verb that ties the build phase to the deploy phase. The full pattern — CI emits a webhook, DeployHQ orchestrates the release, post-deploy curl smoke tests gate the rollout — is documented in [the AI-agent-driven CI/CD walkthrough](https://www.deployhq.com/blog/agentic-workflows-explained-ai-agents-cicd-pipelines). For projects where the deploy lives entirely behind a webhook trigger, see [DeployHQ integrations](https://www.deployhq.com/features/integrations).

[Start a free DeployHQ trial](https://www.deployhq.com/signup) to wire the webhook in five minutes.


## Related cheatsheets

- [Bash scripting cheatsheet](https://www.deployhq.com/cheatsheets/bash) — for the script harness that wraps every curl call (`set -euo pipefail`, lockfiles, exit codes).
- [SSH cheatsheet](https://www.deployhq.com/cheatsheets/ssh) — for the deploy-host side of the same pipeline.
- [jq cheatsheet](https://www.deployhq.com/cheatsheets/jq) — for parsing the JSON your curl calls return.
- [Claude Code cheatsheet](https://www.deployhq.com/cheatsheets/claude-code) — for asking an AI agent to draft, debug, and audit these curl-based deploy hooks.
- [Cheatsheets hub](https://www.deployhq.com/cheatsheets) — every DeployHQ cheatsheet in one place.


Need help? Email [support@deployhq.com](mailto:support@deployhq.com) or follow [@deployhq on X](https://x.com/deployhq).
