cURL: A Complete Guide with Practical Examples
cURL is a command-line tool for transferring data with URLs. It supports HTTP, HTTPS, FTP, SFTP, LDAP, and dozens of other protocols. For developers, cURL is the most common tool for testing APIs, debugging web services, and automating data transfers.
Basic Usage
# Simple GET request
curl https://api.example.com/users
# Include response headers
curl -i https://api.example.com/users
# Only response headers (no body)
curl -I https://api.example.com/users
# Silent mode (no progress meter, no errors)
curl -s https://api.example.com/users
# Silent with error output
curl -sS https://api.example.com/users
# Follow redirects
curl -L http://example.comThe -i flag is useful for debugging — it shows both the HTTP status code and headers alongside the response body. -I sends a HEAD request, which is useful for checking if a resource exists without downloading it.
HTTP Methods
# GET (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":"alice@example.com"}'
# PUT (update)
curl -X PUT https://api.example.com/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Updated"}'
# PATCH (partial update)
curl -X PATCH https://api.example.com/users/1 \
-H "Content-Type: application/json" \
-d '{"email":"new@example.com"}'
# DELETE
curl -X DELETE https://api.example.com/users/1The -d flag sends data in the request body and automatically sets Content-Type: application/x-www-form-urlencoded unless you override it with -H. Always set Content-Type explicitly when sending JSON.
Headers and Authentication
Custom Headers
# Single header
curl -H "Authorization: Bearer token123" https://api.example.com/users
# Multiple headers
curl -H "Authorization: Bearer token123" \
-H "Accept: application/json" \
-H "X-Custom-Header: value" \
https://api.example.com/users
# User agent
curl -A "Mozilla/5.0 (X11; Linux x86_64)" https://example.comAuthentication Methods
# Basic auth (username:password)
curl -u alice:secret https://api.example.com/protected
# Bearer token (alternative to -H)
# Not built-in, use -H instead
# Digest auth
curl --digest -u alice:secret https://api.example.com/digest
# OAuth 2.0 with bearer token in query param
curl "https://api.example.com/data?access_token=token123"For Basic auth, -u sends the credentials as an HTTP header encoded in Base64. This is not encrypted — always use HTTPS with Basic auth, never HTTP.
URL Parameters
# Query parameters (use -G with -d)
curl -G https://api.example.com/users \
-d "page=1" \
-d "limit=10" \
-d "sort=name"
# URL encoding is automatic
curl -G https://api.example.com/search \
--data-urlencode "q=hello world"
# Manual URL (not recommended)
curl "https://api.example.com/users?page=1&limit=10"Using -G with -d is cleaner than building URLs manually. The --data-urlencode flag properly encodes special characters, which is essential for search queries and user-supplied input.
File Uploads
# Upload a file (multipart/form-data)
curl -F "file=@photo.jpg" https://api.example.com/upload
# Multiple fields
curl -F "file=@photo.jpg" \
-F "description=Vacation photo" \
-F "album=summer" \
https://api.example.com/upload
# Upload with different filename
curl -F "file=@photo.jpg;filename=profile.jpg" \
https://api.example.com/upload
# Binary data upload
curl --data-binary "@data.bin" https://api.example.com/ingestThe -F flag automatically sets Content-Type: multipart/form-data with proper boundaries. For simple file uploads, this is all you need. The server sees the file with its original filename unless you override it with ;filename=.
File Downloads
# Download and save with original filename
curl -O https://example.com/file.zip
# Save with custom filename
curl -o output.zip https://example.com/file.zip
# Resume download
curl -C - -O https://example.com/large-file.zip
# Download multiple files
curl -O https://example.com/file1.zip \
-O https://example.com/file2.zip
# Limit download rate (1 MB/s)
curl --limit-rate 1M -O https://example.com/large-file.zipDebugging and Verbose Output
# Verbose mode (shows request and response headers)
curl -v https://api.example.com/users
# Very verbose (includes SSL/TLS details)
curl --trace-ascii trace.txt https://api.example.com/users
# Full trace with hex dump
curl --trace trace.txt https://api.example.com/users
# Show timing breakdown
curl -w "\nTime: %{time_total}s\n" https://api.example.com/users
# Comprehensive timing
curl -w "\n\
DNS: %{time_namelookup}s\n\
Connect: %{time_connect}s\n\
TLS: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
https://api.example.com/usersThe -v flag is your primary debugging tool. It shows every header sent and received, the SSL certificate chain, and any redirects. For deeper debugging, --trace-ascii captures the full request and response including binary data.
REST API Testing Workflow
Full API Test Script
#!/bin/bash
BASE="https://api.example.com"
TOKEN="your-token-here"
# 1. Health check
echo "=== Health Check ==="
curl -sS "$BASE/health" | jq .
# 2. List users
echo "=== List Users ==="
curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/users" | jq .
# 3. Create user
echo "=== Create User ==="
NEW_USER=$(curl -sS -X POST "$BASE/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com"}')
echo "$NEW_USER" | jq .
USER_ID=$(echo "$NEW_USER" | jq -r '.id')
# 4. Update user
echo "=== Update User ==="
curl -sS -X PUT "$BASE/users/$USER_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Updated Name"}' | jq .
# 5. Delete user
echo "=== Delete User ==="
curl -sS -X DELETE "$BASE/users/$USER_ID" \
-H "Authorization: Bearer $TOKEN"
echo ""Cookie-Based Authentication
# Login and save cookies
curl -c cookies.txt -X POST https://example.com/login \
-d "username=alice&password=secret"
# Use cookies for authenticated requests
curl -b cookies.txt https://example.com/profile
# Login in one command (capture cookie)
curl -c - -X POST https://example.com/login \
-d "username=alice&password=secret"Sending Different Data Formats
# JSON (explicit flag)
curl -X POST https://api.example.com/data \
--json '{"key": "value"}'
# URL-encoded form
curl -X POST https://api.example.com/form \
-d "name=Alice" \
-d "age=30"
# Raw data
curl -X POST https://api.example.com/raw \
-H "Content-Type: text/plain" \
-d "Hello, server!"
# From a file
curl -X POST https://api.example.com/import \
-H "Content-Type: application/json" \
-d @data.jsonError Handling and Exit Codes
cURL returns specific exit codes that you can check in scripts:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unsupported protocol |
| 6 | Could not resolve host |
| 7 | Failed to connect |
| 22 | HTTP page not retrieved |
| 28 | Operation timeout |
| 35 | SSL connect error |
| 55 | Failed sending network data |
| 56 | Failure in receiving network data |
Always check exit codes in automated scripts: if [ $? -ne 0 ]; then echo "Request failed"; fi. Combine with --retry and --retry-delay for robust production scripts.
Best Practices
- Use
-sSin scripts — silent mode suppresses progress, but errors are still shown - Always check exit codes —
curlreturns 0 on success, non-zero on failure - Limit redirects with
--max-redirects 5to prevent infinite loops - Set timeouts —
--connect-timeout 10 --max-time 30for production scripts - Verify SSL — do not use
-k(insecure) in production; fix certificate issues properly - Rate limiting — add
--sleep 1between requests to avoid overwhelming APIs
FAQ
What is the difference between -X POST and -d?
The -d flag sends data and implicitly sets the request method to POST. You only need -X POST when you want to be explicit or when -d is not used. Some APIs require GET with body data, though this is non-standard.
How do I send gzipped data with cURL?
Use --data-binary @file.gz with the header Content-Encoding: gzip. Note that the server must be configured to accept compressed request bodies, as most web servers handle only compressed responses.
Why does cURL return “SSL certificate problem”?
This means the server’s SSL certificate is self-signed, expired, or doesn’t match the hostname. Use -k only for testing. For production, fix the certificate issue or add the CA certificate with --cacert.
How do I mock slow responses with cURL?
Use --limit-rate to simulate slow connections for download testing. For upload testing, combine with --expect100-timeout to test how your application handles slow server responses.
Can cURL send concurrent requests?
cURL is single-threaded by nature. For concurrent requests, use tools like xargs with -P for parallelism, or use curl --parallel (available in newer versions with parallel support enabled).
Related: Learn SSH keys setup and REST API vs GraphQL.
Advanced Curl Techniques
Beyond basic HTTP requests, curl offers powerful features for API debugging and automation. The -w flag outputs connection timing details, invaluable for performance profiling: curl -w "@timing-format.txt" -o /dev/null https://api.example.com. Curl’s --resolve flag overrides DNS resolution for a specific host, enabling requests to a load balancer’s IP while preserving the Host header. Session cookies can be captured and replayed with -c cookies.txt and -b cookies.txt, simulating browser-like multi-step flows. For GraphQL APIs, curl’s -d with proper Content-Type: application/json and query escaping lets you test mutations and queries directly from the terminal. The --parallel flag (curl 7.68+) dispatches multiple transfers simultaneously: curl --parallel --parallel-immediate -O url1 -O url2. Combined with jq for JSON parsing, curl becomes a complete API testing toolkit. Rate-limited endpoints can be tested with --limit-rate 100K to simulate throttled connections. The --retry flag automatically retries failed requests up to a specified count, with --retry-delay controlling the pause between attempts.
Curl in CI/CD Pipelines
Curl is a staple in continuous integration scripts. Health-check endpoints are verified with curl --retry 5 --retry-delay 10 --retry-connrefused http://service:8080/health. API deployment verification uses HTTP status code inspection via -o /dev/null -w "%{http_code}". Access tokens stored as environment variables are passed with -H "Authorization: Bearer $TOKEN". Curl’s silent mode (-sS) suppresses progress output while showing errors, keeping CI logs clean. Upload artifacts to artifact repositories with --upload-file and proper content-type headers. Curl’s --fail flag exits with a non-zero code on HTTP errors, which most CI systems interpret as a pipeline failure.