curl HTTP Client
HTTP client operations and API testing.
HTTP Fundamentals
# HTTP methods
curl -X GET https://api.example.com/users # Default, explicit GET
curl -X POST https://api.example.com/users # Create
curl -X PUT https://api.example.com/users/1 # Update (full)
curl -X PATCH https://api.example.com/users/1 # Update (partial)
curl -X DELETE https://api.example.com/users/1 # Delete
curl -X HEAD https://api.example.com/users # Headers only (like -I)
curl -X OPTIONS https://api.example.com/users # CORS preflight check
# Response handling
curl -I https://example.com # Headers only
curl -i https://example.com # Headers + body
curl -s https://example.com # Silent (no progress)
curl -S https://example.com # Show errors with -s
curl -f https://example.com # Fail silently on HTTP errors
curl -L https://example.com # Follow redirects (302/301)
curl -L --max-redirs 5 https://example.com # Limit redirect depth
# Output control
curl -o file.html https://example.com # Save to specific file
curl -O https://example.com/file.tar.gz # Save with remote filename
curl -o /dev/null -s https://example.com # Discard body (timing tests)
curl -w '%{http_code}\n' -o /dev/null -s URL # Just status code
# Connection tuning
curl --connect-timeout 5 https://example.com # Connection timeout (seconds)
curl -m 30 https://example.com # Max total time
curl --retry 3 https://example.com # Retry on transient errors
curl --retry-delay 2 https://example.com # Wait between retries
Headers and Content Types
# Setting headers
curl -H "Content-Type: application/json" URL
curl -H "Accept: application/json" URL
curl -H "X-Custom-Header: value" URL
curl -H "Host: api.example.com" http://10.0.0.1 # Override Host header
# Multiple headers
curl -H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Request-ID: $(uuidgen)" \
URL
# Content negotiation
curl -H "Accept: application/xml" URL # Request XML
curl -H "Accept: text/plain" URL # Request plain text
curl -H "Accept-Language: es" URL # Spanish content
curl -H "Accept-Encoding: gzip" URL # Compressed response
# Common content types
# application/json - JSON API
# application/xml - XML/SOAP
# application/x-www-form-urlencoded - HTML form default
# multipart/form-data - File uploads
# text/plain - Raw text
# application/octet-stream - Binary data
Authentication Patterns
# Basic auth (username:password base64 encoded)
curl -u user:pass https://api.example.com
curl -u admin https://api.example.com # Prompt for password
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" URL
# Bearer token (OAuth2, JWT)
curl -H "Authorization: Bearer $TOKEN" URL
curl -H "Authorization: Bearer $(cat /tmp/token)" URL
# API key patterns
curl -H "X-API-Key: $API_KEY" URL # Header-based
curl "https://api.example.com?api_key=$API_KEY" # Query param (less secure)
# Digest auth
curl --digest -u user:pass https://api.example.com
# Client certificates (mTLS)
curl --cert /path/to/cert.pem --key /path/to/key.pem URL
curl --cert /path/to/cert.pem:password URL # If key has passphrase
curl --cacert /path/to/ca.pem URL # Custom CA trust
# Negotiate (Kerberos/SPNEGO)
curl --negotiate -u : URL # Use Kerberos ticket
# AWS Signature v4
curl --aws-sigv4 "aws:amz:us-east-1:s3" \
-u "$AWS_ACCESS_KEY:$AWS_SECRET_KEY" URL
POST Data and Forms
# URL-encoded form data (default Content-Type)
curl -d "username=admin&password=secret" URL
curl -d "key1=value1" -d "key2=value2" URL # Multiple fields
curl --data-urlencode "query=hello world" URL # URL-encode value
# JSON POST
curl -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"secret"}' \
URL
# JSON from file
curl -X POST \
-H "Content-Type: application/json" \
-d @/tmp/payload.json \
URL
# JSON from stdin (heredoc)
curl -X POST \
-H "Content-Type: application/json" \
-d @- URL <<'EOF'
{
"name": "test",
"enabled": true,
"tags": ["production", "critical"]
}
EOF
# File upload (multipart/form-data)
curl -F "file=@/path/to/file.pdf" URL
curl -F "file=@report.pdf;filename=monthly.pdf" URL # Rename on upload
curl -F "file=@doc.pdf;type=application/pdf" URL # Explicit MIME type
curl -F "file=@image.png" -F "description=logo" URL # File + field
# Binary data
curl -X POST --data-binary @/path/to/binary.bin URL
curl -X POST --data-binary "@-" URL < /tmp/data # From stdin
HashiCorp Vault API
# Environment setup
export VAULT_ADDR="https://vault-01.inside.domusdigitalis.dev:8200"
export VAULT_TOKEN="hvs.CAESIP..." # Or from file: $(cat ~/.vault-token)
# Check Vault health (no auth needed)
curl -sk "$VAULT_ADDR/v1/sys/health" | jq
curl -sk "$VAULT_ADDR/v1/sys/seal-status" | jq '.sealed'
# KV v2 secrets (note /data/ in path)
curl -sk -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/kv/data/myapp/config" | jq '.data.data'
# Write KV secret
curl -sk -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"data":{"username":"admin","password":"secret"}}' \
"$VAULT_ADDR/v1/kv/data/myapp/config"
# PKI - Issue certificate
curl -sk -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"common_name":"myhost.inside.domusdigitalis.dev","ttl":"8760h"}' \
"$VAULT_ADDR/v1/pki_int/issue/domus-client" | jq
# SSH CA - Sign public key
curl -sk -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"public_key\":\"$(cat ~/.ssh/id_ed25519.pub)\",\"valid_principals\":\"ansible,evanusmodestus,root\"}" \
"$VAULT_ADDR/v1/ssh/sign/domus-client" | jq -r '.data.signed_key' > ~/.ssh/id_ed25519-cert.pub
# List secrets (careful: LIST uses different path pattern)
curl -sk -X LIST -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/kv/metadata/" | jq '.data.keys'
# AppRole authentication
curl -sk -X POST \
-d '{"role_id":"'"$ROLE_ID"'","secret_id":"'"$SECRET_ID"'"}' \
"$VAULT_ADDR/v1/auth/approle/login" | jq -r '.auth.client_token'
Cisco ISE ERS API
# Environment setup (use dsource to load these)
# dsource d000 dev/network
# Required: ISE_HOST, ISE_USER, ISE_PASS
ISE_BASE="https://$ISE_HOST:9060/ers/config"
ISE_AUTH="$ISE_USER:$ISE_PASS"
# NOTE: ISE requires both Accept AND Content-Type headers
# List endpoints
curl -sk -u "$ISE_AUTH" \
-H "Accept: application/json" \
"$ISE_BASE/endpoint" | jq '.SearchResult.resources[:5]'
# Get specific endpoint by ID
curl -sk -u "$ISE_AUTH" \
-H "Accept: application/json" \
"$ISE_BASE/endpoint/$ENDPOINT_ID" | jq '.Endpoint'
# Create endpoint
curl -sk -X POST -u "$ISE_AUTH" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"Endpoint": {
"name": "Test Endpoint",
"mac": "AA:BB:CC:DD:EE:FF",
"staticGroupAssignment": true,
"groupId": "'"$GROUP_ID"'"
}
}' \
"$ISE_BASE/endpoint"
# Update endpoint
curl -sk -X PUT -u "$ISE_AUTH" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"Endpoint": {
"id": "'"$ENDPOINT_ID"'",
"name": "Updated Name",
"mac": "AA:BB:CC:DD:EE:FF"
}
}' \
"$ISE_BASE/endpoint/$ENDPOINT_ID"
# Delete endpoint
curl -sk -X DELETE -u "$ISE_AUTH" \
-H "Accept: application/json" \
"$ISE_BASE/endpoint/$ENDPOINT_ID"
# Search with filters
curl -sk -u "$ISE_AUTH" \
-H "Accept: application/json" \
"$ISE_BASE/endpoint?filter=mac.EQ.AA:BB:CC:DD:EE:FF" | jq
# List downloadable ACLs
curl -sk -u "$ISE_AUTH" \
-H "Accept: application/json" \
"$ISE_BASE/downloadableacl" | jq '.SearchResult.resources[].name'
Kubernetes API
# Get API server URL and token
K8S_API=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')
K8S_TOKEN=$(kubectl get secret -n default \
$(kubectl get sa default -o jsonpath='{.secrets[0].name}') \
-o jsonpath='{.data.token}' | base64 -d)
# Or use service account token (inside pod)
K8S_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
K8S_CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# List pods
curl -sk -H "Authorization: Bearer $K8S_TOKEN" \
"$K8S_API/api/v1/namespaces/default/pods" | jq '.items[].metadata.name'
# Get specific pod
curl -sk -H "Authorization: Bearer $K8S_TOKEN" \
"$K8S_API/api/v1/namespaces/default/pods/mypod" | jq
# Create pod (from JSON)
curl -sk -X POST -H "Authorization: Bearer $K8S_TOKEN" \
-H "Content-Type: application/json" \
-d @pod.json \
"$K8S_API/api/v1/namespaces/default/pods"
# Delete pod
curl -sk -X DELETE -H "Authorization: Bearer $K8S_TOKEN" \
"$K8S_API/api/v1/namespaces/default/pods/mypod"
# Watch pods (Server-Sent Events)
curl -sk -H "Authorization: Bearer $K8S_TOKEN" \
"$K8S_API/api/v1/namespaces/default/pods?watch=true"
# Get cluster info
curl -sk -H "Authorization: Bearer $K8S_TOKEN" \
"$K8S_API/api/v1" | jq '.resources[].name'
# NOTE: For production, use kubectl or client libraries
# curl is useful for debugging API access issues
Wazuh API
# Environment setup (from dsource)
# dsource d000 dev/observability
# Required: WAZUH_API_URL, WAZUH_API_USER, WAZUH_API_PASSWORD
# Get JWT token (required for all API calls)
TOKEN=$(curl -sk -X POST \
-H "Content-Type: application/json" \
-d '{"username":"'"$WAZUH_API_USER"'","password":"'"$WAZUH_API_PASSWORD"'"}' \
"$WAZUH_API_URL/security/user/authenticate" | jq -r '.data.token')
# Check manager status
curl -sk -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/manager/status" | jq '.data.affected_items'
# List agents
curl -sk -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/agents" | jq '.data.affected_items[] | {id, name, status}'
# Get agent details
curl -sk -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/agents/001" | jq '.data.affected_items[0]'
# Restart agent
curl -sk -X PUT -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/agents/001/restart"
# List rules
curl -sk -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/rules?limit=10" | jq '.data.affected_items[] | {id, description, level}'
# Get active alerts (last 24h)
curl -sk -H "Authorization: Bearer $TOKEN" \
"$WAZUH_API_URL/alerts?limit=100&sort=-timestamp" | jq
Debugging and Timing
# Verbose output (-v = --verbose)
curl -v https://example.com 2>&1 | grep -E '^[<>*]'
# Even more verbose (TLS handshake details)
curl -vvv https://example.com
# Trace to file
curl --trace /tmp/curl-trace.log https://example.com
curl --trace-ascii /tmp/curl-trace.txt https://example.com # Readable
# Timing information
curl -w "@-" -o /dev/null -s https://example.com <<'EOF'
DNS Lookup: %{time_namelookup}s
TCP Connect: %{time_connect}s
TLS Setup: %{time_appconnect}s
First Byte: %{time_starttransfer}s
Total Time: %{time_total}s
HTTP Code: %{http_code}
Size: %{size_download} bytes
EOF
# One-liner timing
curl -w "Time: %{time_total}s | Code: %{http_code}\n" \
-o /dev/null -s https://example.com
# Check HTTP response code only
http_code=$(curl -o /dev/null -s -w '%{http_code}' URL)
[[ "$http_code" == "200" ]] && echo "OK" || echo "FAIL: $http_code"
# Get effective URL (after redirects)
curl -Ls -o /dev/null -w '%{url_effective}' https://bit.ly/shorturl
# Show redirect chain
curl -sIL https://bit.ly/shorturl 2>&1 | grep -i 'location:'
TLS/SSL Operations
# Skip certificate verification (development only!)
curl -k https://self-signed.example.com
curl --insecure https://self-signed.example.com
# Use specific CA bundle
curl --cacert /path/to/ca-bundle.crt https://internal.example.com
# Client certificate authentication (mTLS)
curl --cert /etc/ssl/certs/client.pem \
--key /etc/ssl/private/client.key \
https://mtls.example.com
# With passphrase-protected key
curl --cert /path/to/cert.pem:passphrase \
--key /path/to/key.pem \
https://mtls.example.com
# Show certificate chain
curl -vvI https://example.com 2>&1 | grep -A2 'Server certificate'
# Force TLS version
curl --tlsv1.2 https://example.com # Minimum TLS 1.2
curl --tls-max 1.2 https://example.com # Maximum TLS 1.2
curl --tlsv1.3 https://example.com # Require TLS 1.3
# Check certificate expiry
curl -svI https://example.com 2>&1 | grep 'expire date'
# Alternative: openssl for detailed cert inspection
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates
Scripting Patterns
# Health check with retry
check_health() {
local url=$1 retries=${2:-3} delay=${3:-5}
for ((i=1; i<=retries; i++)); do
if curl -sf -o /dev/null "$url"; then
echo "OK: $url"
return 0
fi
echo "Attempt $i/$retries failed, retrying in ${delay}s..."
sleep "$delay"
done
echo "FAIL: $url unreachable after $retries attempts"
return 1
}
# Usage
check_health "https://vault-01.inside.domusdigitalis.dev:8200/v1/sys/health"
# Parallel API calls (background jobs)
for endpoint in users groups policies; do
curl -s "https://api.example.com/$endpoint" > "/tmp/$endpoint.json" &
done
wait # Wait for all background jobs
# Rate-limited requests
for id in {1..100}; do
curl -s "https://api.example.com/users/$id" >> /tmp/users.jsonl
sleep 0.1 # 10 requests per second max
done
# API pagination
page=1
while true; do
response=$(curl -s "https://api.example.com/users?page=$page&per_page=100")
count=$(echo "$response" | jq '.data | length')
[[ "$count" -eq 0 ]] && break
echo "$response" | jq -c '.data[]' >> /tmp/all-users.jsonl
((page++))
done
# Error handling
response=$(curl -sf -w '\n%{http_code}' "https://api.example.com/data")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | head -n -1)
case "$http_code" in
200) echo "Success: $body" ;;
401) echo "Auth failed" >&2; exit 1 ;;
404) echo "Not found" >&2; exit 1 ;;
5*) echo "Server error: $http_code" >&2; exit 1 ;;
*) echo "Unexpected: $http_code" >&2; exit 1 ;;
esac
Infrastructure Patterns
# Multi-host health check
HOSTS=(
"https://vault-01.inside.domusdigitalis.dev:8200/v1/sys/health"
"https://ise-01.inside.domusdigitalis.dev:9060/ers/sdk"
"https://wazuh.inside.domusdigitalis.dev:443"
"https://grafana.inside.domusdigitalis.dev:443/api/health"
)
printf "%-50s %s\n" "ENDPOINT" "STATUS"
for url in "${HOSTS[@]}"; do
code=$(curl -sk -o /dev/null -w '%{http_code}' --connect-timeout 5 "$url")
status=$([[ "$code" =~ ^2 ]] && echo "✓ OK" || echo "✗ FAIL ($code)")
printf "%-50s %s\n" "$url" "$status"
done
# Certificate expiry check across hosts
for host in vault-01 ise-01 wazuh; do
echo "=== $host ==="
echo | timeout 5 openssl s_client -connect "$host.inside.domusdigitalis.dev:443" \
-servername "$host.inside.domusdigitalis.dev" 2>/dev/null | \
openssl x509 -noout -dates | awk -F= '/notAfter/{print $2}'
done
# Download with resume support (large files)
curl -C - -O https://example.com/large-file.iso
# Mirror website (basic)
curl -O -L --remote-name-all https://example.com/{page1,page2,page3}.html
# Conditional download (only if modified)
curl -z /tmp/cached.json -o /tmp/cached.json https://api.example.com/data.json
# Webhook notification
curl -X POST \
-H "Content-Type: application/json" \
-d '{"text":"Deployment complete on '"$(hostname)"'"}' \
"https://hooks.slack.com/services/XXX/YYY/ZZZ"
Common Gotchas
# WRONG: Variable in single quotes (not expanded)
curl -d '{"name":"$USER"}' URL
# CORRECT: Use double quotes for variables
curl -d '{"name":"'"$USER"'"}' URL
# Or use jq for safe JSON construction
curl -d "$(jq -n --arg name "$USER" '{name: $name}')" URL
# WRONG: Missing Content-Type for JSON
curl -X POST -d '{"key":"value"}' URL
# CORRECT: Always specify Content-Type
curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' URL
# WRONG: Assuming HTTP 200 means success
curl https://api.example.com/delete/123
# CORRECT: Check exit code AND HTTP status
if curl -sf -o /dev/null https://api.example.com/delete/123; then
echo "Success"
else
echo "Failed"
fi
# WRONG: Passwords in command line (visible in ps)
curl -u admin:secret URL
# CORRECT: Use netrc file or prompt
curl -n URL # Use ~/.netrc
curl -u admin URL # Prompt for password
# WRONG: Ignoring redirects
curl https://example.com # May get 301/302
# CORRECT: Follow redirects
curl -L https://example.com
# WRONG: Not URL-encoding special characters
curl "https://api.example.com/search?q=hello world"
# CORRECT: URL encode
curl --data-urlencode "q=hello world" https://api.example.com/search
curl "https://api.example.com/search?q=$(printf '%s' 'hello world' | jq -sRr @uri)"
Quick Reference
# Essential flags
-s # Silent (no progress)
-S # Show errors (with -s)
-f # Fail silently on HTTP errors (non-zero exit)
-L # Follow redirects
-k # Skip TLS verification
-v # Verbose output
-I # HEAD request (headers only)
-i # Include headers in output
-o FILE # Output to file
-O # Save with remote filename
-d DATA # POST data
-H HEADER # Add header
-u USER:PASS # Basic auth
-X METHOD # HTTP method
-m SECONDS # Max time
--connect-timeout SECONDS
-w FORMAT # Write-out format
# Common status codes
# 200 OK
# 201 Created
# 204 No Content
# 301 Moved Permanently
# 302 Found (temporary redirect)
# 400 Bad Request
# 401 Unauthorized
# 403 Forbidden
# 404 Not Found
# 429 Too Many Requests
# 500 Internal Server Error
# 502 Bad Gateway
# 503 Service Unavailable
# Write-out variables
# %{http_code} - HTTP status code
# %{time_total} - Total time in seconds
# %{time_connect} - Time to connect
# %{time_namelookup} - DNS lookup time
# %{size_download} - Downloaded bytes
# %{url_effective} - Final URL after redirects