curl is the most widespread data transfer tool in the DevOps ecosystem,
installed by default on almost every Linux distribution. It is used to test a
REST API in one line, download files, automate webhooks or debug
network problems. Unlike Postman, curl runs directly in a terminal or a shell
script, which makes it essential in CI/CD pipelines and system
administration.
What you will learn
- Send requests: GET, POST, PUT and DELETE, with headers and a JSON body
- Download and upload files, over HTTP as well as FTP
- Authenticate a request: Basic, Bearer token,
-Kconfiguration file - Diagnose an exchange with
-vand the--write-outmetrics - Harden curl in a script:
--fail,--silent, configuration files
curl cheat sheet
This table gathers the options you will use day to day, to keep at hand for
quick access. Each one is detailed below with a runnable example. Two
landmarks to read it: options in upper and lower case do not do the same
thing (-o names the file, -O keeps the remote name), and the last two rows
(-w, --silent --fail) are the ones that make curl reliable in a script.
| Command | Description |
|---|---|
curl URL | Simple GET request |
curl -o fichier URL | Download into a named file |
curl -O URL | Download keeping the remote name |
curl -I URL | Show only the HTTP headers |
curl -L URL | Follow redirects |
curl -X POST -d "data" URL | Send POST data |
curl -H "Header: value" URL | Add a custom header |
curl -u user:pass URL | HTTP Basic authentication |
curl -F "file=@path" URL | Upload a file (multipart) |
curl -c cookies.txt URL | Save the cookies |
curl -b cookies.txt URL | Send the cookies |
curl -w "%{http_code}" URL | Print the HTTP code |
curl --silent --fail URL | Silent mode for scripts |
What curl can do
Curl (short for Client URL) is a command-line tool designed to transfer data to or from a server, using protocols such as HTTP, HTTPS, FTP, SFTP, SCP, LDAP or SMTP.
It is used as much to:
- Download a file from the Internet,
- Test the endpoints of a REST API,
- Submit HTML forms,
- Automate file transfers,
- Run complex HTTP requests with headers, authentication or cookies.
Unlike heavier tools such as Postman, curl is used directly from a
terminal, in scripts, or inside DevOps pipelines. Its lightness and its
multi-protocol support make it a remarkably effective diagnostic and
automation tool, whether you are a developer or a system administrator.
Installation
curl is almost always already present on a Linux distribution. The first two
tabs install the repository version of your distribution, more than enough
for everyday use. The third tab only concerns the case where you need a recent
feature missing from the packaged version: it builds 8.22.0 from the official
sources, in your home directory so as not to overwrite the system binary.
Before building, it verifies the GPG signature of the archive: gpg --verify
must print Good signature from "Daniel Stenberg" and the fingerprint
27ED EAF2 2F3A BCEB 50DB 9A12 5CC9 08FD B71E 12C2 (checked on 2026-10-01).
Without libpsl-dev, ./configure stops on
libpsl libs and/or directories were not found (observed on Debian 13).
# Version du dépôt (généralement plus ancienne)sudo apt update && sudo apt install -y curl
# Vérifier la versioncurl --versionsudo dnf install -y curlcurl --versionTo get version 8.22.0:
# Dépendancessudo apt install -y build-essential libssl-dev libnghttp2-dev libpsl-dev
# Télécharger l'archive et sa signaturecd /tmpcurl -LO https://curl.se/download/curl-8.22.0.tar.gzcurl -LO https://curl.se/download/curl-8.22.0.tar.gz.asc
# Vérifier la signature de Daniel Stenberg avant de compilercurl -L -o daniel.asc https://daniel.haxx.se/mykey.ascgpg --import daniel.ascgpg --verify curl-8.22.0.tar.gz.asc curl-8.22.0.tar.gz
# Compilertar xzf curl-8.22.0.tar.gzcd curl-8.22.0./configure --with-openssl --prefix=$HOME/.localmake -j$(nproc)make install
# Ajouter au PATHexport PATH="$HOME/.local/bin:$PATH"Check the installation:
curl --versioncurl 8.22.0 (x86_64-pc-linux-gnu) libcurl/8.22.0 OpenSSL/3.5.7 zlib/1.3.1 libpsl/0.21.2 nghttp2/1.64.0Release-Date: 2026-09-02Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt mqtts pop3 pop3s rtsp smtp smtps telnet tftp ws wssFeatures: alt-svc AsynchDNS HSTS HTTP2 HTTPS-proxy IPv6 Largefile libz PSL SSL threadsafe UnixSocketsBasic syntax
The curl command relies on a simple and direct structure. In its minimal
form, it is made of a URL, but its power comes from the many options
you can add as needed.
curl [options] [URL]Essential options
Here are the most used options to get started, each with the command that
shows it at work. The lowercase -o and the uppercase -O are the
first pair to tell apart.
-
-o fichier: saves the content in a local file namedfichier.Terminal window curl -o page.html https://example.com -
-O: downloads the file keeping its original name on the server.Terminal window curl -O https://example.com/fichier.zip -
-L: follows HTTP redirects automatically (very useful on shortened or moved URLs).Terminal window curl -L https://example.com/ancien-lien -
-I: shows only the HTTP headers.Terminal window curl -I https://example.com -
-X: sets an HTTP method (GET, POST, PUT, DELETE…).Terminal window curl -X DELETE https://api.example.com/ressource -
-d: sends data in the request body (POST by default).Terminal window curl -d "nom=Jean&age=30" https://example.com/formulaire -
-H: adds a custom header (e.g. JSON, token, cookie...).Terminal window curl -H "Content-Type: application/json" https://api.example.com -
-u: uses HTTP Basic authentication.Terminal window curl -u admin:motdepasse https://example.com/admin
Common uses
One of the most frequent uses of curl is downloading content from a URL.
It can be HTML pages, archives, images or scripts. Here is how to do it
efficiently.
Saving to a specific file
To download a page or a file and choose the local name:
curl -o page.html https://example.com % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 513 0 513 0 0 10421 0 0This writes the content of the response into a file named page.html.
Showing only the HTTP headers
The -I option fetches the metadata without downloading the content:
curl -I https://example.comHTTP/2 200date: Sat, 24 Jan 2026 08:44:59 GMTcontent-type: text/htmlcf-ray: 9c2e40871e88791b-CDGlast-modified: Fri, 23 Jan 2026 20:51:32 GMTallow: GET, HEADaccept-ranges: bytesage: 3591cf-cache-status: HITserver: cloudflareKeeping the remote file name
If you want to fetch a file while keeping its original name (taken from the URL):
curl -O https://example.com/script.shThis creates a script.sh file in the current directory.
Following redirects
Some links redirect you automatically (HTTP 301 or 302). Without any option,
curl does not follow them. You must add -L:
curl -LO https://bit.ly/exemple-lienThis lets you reach the final URL and fetch the right content. Watch the
name: -O takes it from the URL you typed, not from the final URL.
Here, the file is therefore called exemple-lien. Force the name with -o, or
add -J when the server announces a name in the Content-Disposition header.
Resuming an interrupted download
curl can resume a download if the connection is interrupted:
curl -C - -O https://example.com/long-fichier.zipThe -C - option detects the size of the existing file and continues from
there.
Downloading several files
Use several -O options:
curl -O https://site1.com/fichier1.txt -O https://site2.com/fichier2.jpgOr use a shell loop to automate it, reading the file line by line: a
for url in $(cat urls.txt) form would split on spaces and expand
wildcards such as * or ? present in a URL.
while read -r url; do curl -O "$url"; done < urls.txtSending HTTP requests
curl is also a powerful tool to interact with REST APIs. It can send
every kind of HTTP request: GET, POST, PUT, DELETE, with data, headers
or authentication. Responses almost always come in
JSON format (in French), which you can
filter with jq.
GET request (default)
The GET method is the one used by default. It fetches data without sending
any.
curl -s https://httpbin.org/get{ "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/8.22.0", "X-Amzn-Trace-Id": "Root=1-69748697-791d1c236921ff1653569139" }, "origin": "86.229.137.201", "url": "https://httpbin.org/get"}POST request with simple data
Send application/x-www-form-urlencoded data:
curl -X POST -d "nom=Alice&age=28" https://api.example.com/inscriptionBy default, -d uses POST, so -X POST is optional here.
POST request with JSON
Sending a JSON payload requires setting the Content-Type header:
curl -s -X POST \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"1234"}' \ https://httpbin.org/post{ "args": {}, "data": "{\"email\":\"test@example.com\",\"password\":\"1234\"}", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Content-Length": "46", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "curl/8.22.0" }, "json": { "email": "test@example.com", "password": "1234" }, "origin": "86.229.137.201", "url": "https://httpbin.org/post"}Sending data from a file
Rather than including the JSON inline, use @ to read from a file:
echo '{"nom":"Jean","age":30}' > data.jsoncurl -s -X POST \ -H "Content-Type: application/json" \ -d @data.json \ https://httpbin.org/post{ "data": "{\"nom\":\"Jean\",\"age\":30}", "json": { "age": 30, "nom": "Jean" }}PUT or DELETE request
Simulate modification or deletion requests:
curl -s -X PUT \ -H "Content-Type: application/json" \ -d '{"status":"actif"}' \ https://httpbin.org/put{ "data": "{\"status\":\"actif\"}", "json": { "status": "actif" }}curl -s -X DELETE https://httpbin.org/delete{ "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/8.22.0" }, "json": null}Sending several headers
You can send several custom HTTP headers:
curl -s \ -H "Authorization: Bearer mon_token_secret" \ -H "Accept: application/json" \ https://httpbin.org/headers{ "headers": { "Accept": "application/json", "Authorization": "Bearer mon_token_secret", "Host": "httpbin.org", "User-Agent": "curl/8.22.0" }}Authentication
Many APIs or servers require authentication to access their resources. curl
supports several methods: HTTP Basic, Bearer token, or FTP/SFTP
authentication.
HTTP Basic authentication
It is the simplest form, where you send a user name and a password encoded in base64:
curl -s -u testuser:testpass https://httpbin.org/basic-auth/testuser/testpass{ "authenticated": true, "user": "testuser"}This method works for APIs or administration interfaces protected by a classic login window in the browser.
Authentication through a Bearer header (token)
Many modern APIs use an authentication token (JWT or other) to include in
the Authorization HTTP header:
curl -H "Authorization: Bearer VOTRE_TOKEN" https://api.example.com/profilThis kind of token is often obtained through a POST request in exchange for
credentials (OAuth flow or custom authentication).
Authentication with configuration files
To avoid typing your credentials in clear text or using tokens on the
command line, curl can store options in a file:
Example: curl.conf
--user utilisateur:motdepasse--url https://api.example.com/securiseAnd to use it:
chmod 600 curl.conf # le fichier contient un secretcurl -K curl.confThe password no longer goes through the command line, so neither through
the shell history nor through ps aux. In return, it lives in clear text
in the file: restrict reading it to your account and never commit it to a Git
repository.
FTP or SFTP authentication
curl can also authenticate to an FTP server:
curl -u ftpuser:ftppass ftp://ftp.example.com/fichier.txtOr with SFTP:
curl -u user:sftpmdp sftp://serveur.example.com/dossier/SFTP and SCP depend on how curl was built. The Debian 13 package
supports them, the one from AlmaLinux 10 only handles file, ftp,
ftps, http, https, ipfs and ipns, and answers
curl: (1) Protocol "sftp" not supported. Check the Protocols line of
curl --version before building a script on it.
Using FTP/SFTP
The curl command can also transfer files over the FTP and SFTP
protocols, for reading as well as writing. It is very useful to automate
transfers to remote servers, backups or publications.
Downloading a file over FTP
To fetch a file from an FTP server:
curl -u utilisateur:motdepasse ftp://ftp.example.com/fichier.txt -O-u: provides the credentialsftp://: sets the protocol-O: keeps the original file name
Uploading a file over FTP
To send a file to an FTP server:
curl -T monfichier.txt -u utilisateur:motdepasse ftp://ftp.example.com/-T: gives the file to upload
Uploading a file over SFTP
Same principle as FTP, but with a secure protocol:
curl -T fichier.txt -u user:motdepasse sftp://sftp.example.com/chemin/Listing the content of an FTP directory
To get the list of files of a remote directory:
curl -u utilisateur:motdepasse ftp://ftp.example.com/This returns a raw view of the content of the target directory.
Adding a custom port
If the server does not use the standard ports:
curl -u user:pass ftp://ftp.example.com:2121/fichier.txt -OUploading files
With curl, you can also upload files to a server, in particular through
HTML forms (as multipart/form-data) or through FTP/SFTP.
Uploading a file with POST (multipart/form-data)
To simulate sending a file from an HTML form:
echo "fichier test upload" > upload.txtcurl -s -F "file=@upload.txt" https://httpbin.org/post{ "files": { "file": "fichier test upload\n" }, "headers": { "Content-Type": "multipart/form-data; boundary=------------------------ym8RxeZV33VnKHabQKinXP", "User-Agent": "curl/8.22.0" }}-F: sends the data as a form@: indicates that the content of the file must be sent
Uploading several files in a single request
You can send several fields, including several files:
curl -F "photo1=@image1.jpg" -F "photo2=@image2.jpg" https://example.com/galerieAnd also add text fields at the same time:
curl -F "titre=Vacances" -F "photo=@plage.jpg" https://example.com/albumUploading through PUT (REST API)
Some APIs accept files sent with a PUT method:
curl -T script.sh -X PUT https://api.example.com/scripts/In HTTP, -T already sends a PUT: the -X PUT is redundant. And
since the URL ends with /, curl appends the file name to it, so the
request goes to /scripts/script.sh.
You can also set a specific MIME header:
curl -T data.csv -H "Content-Type: text/csv" https://api.example.com/donneesAdvanced use
Beyond simple requests, curl offers powerful options for more complex uses:
proxy, user-agent, cookies, debug, configuration, etc.
Using a proxy
To send a request through an HTTP proxy server:
curl -x http://proxy.mondomaine.local:3128 https://example.comAnd for a proxy with authentication:
curl -x http://proxy:port -U utilisateur:motdepasse https://example.comSetting a custom User-Agent
Some servers answer differently depending on the browser simulated. To change the client identifier:
curl -s -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \ https://httpbin.org/user-agent{ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"}Handling cookies
To test a site that keeps a session, curl can save the cookies received
in a file, then send them back on the next request. Two options answer each
other: -c (cookie jar) writes the received cookies, -b reads them back and
sends them. The sequence below chains both to replay a session in full.
-
Save the cookies to a file:
Terminal window curl -s -c cookies.txt -L https://httpbin.org/cookies/set/session/abc123 -
Check the cookies file:
Terminal window cat cookies.txtOutput # Netscape HTTP Cookie Filehttpbin.org FALSE / FALSE 0 session abc123 -
Reuse the cookies:
Terminal window curl -s -b cookies.txt https://httpbin.org/cookiesOutput {"cookies": {"session": "abc123"}}
Showing request metrics
The -w option (write-out) extracts metrics:
curl -s -w "\n--- Métriques ---\nCode HTTP: %{http_code}\nTemps total: %{time_total}s\nTaille: %{size_download} octets\n" \ -o /dev/null https://example.com--- Métriques ---Code HTTP: 200Temps total: 0.074715sTaille: 513 octetsLimiting the bandwidth
Simulate a slow connection with --limit-rate:
curl --limit-rate 100k -O https://example.com/film.mp4Using a configuration file
You can save curl options in a text file:
options.conf:
--user utilisateur:mdp--header "Accept: application/json"--url https://api.example.com/dataAnd run:
curl -K options.confGood practices
To get the most out of curl, adopt these habits that make your commands
safer, more readable and more reusable.
Always check the installed version
Some features may not be available in older versions:
curl --versionThis also gives you the supported protocols (HTTP/2, SSL, etc.).
Using --silent for automated scripts
To avoid noise in scripts (progress bars or minor errors):
curl --silent --fail --show-error -o fichier.txt https://example.com--silent: removes the progress bar and the error messages, not the downloaded content--fail: returns exit code 22 if the HTTP code is ≥ 400--show-error: shows again the error messages hidden by--silent
Preferring configuration files for long commands
Rather than typing a 5-line command, put the options in a curl.conf file and
run:
curl -K curl.confIt is more readable, versionable and easy to review, as long as the file
contains no secret. Credentials go into a second file in chmod 600, outside
the repository, or go through standard input as described in the next
section.
Always setting headers explicitly for APIs
Many APIs require Content-Type: application/json. Be explicit:
curl -H "Content-Type: application/json" ...Never exposing passwords in shared scripts
Avoid hard-coded credentials in a script, but also beware of the false
good idea of variables: curl -u "$API_USER:$API_PASS" is expanded by the
shell before the call, and the secret ends up in the process arguments.
curl tries to blank the -u value once started, but a ps run at the same
moment still reads it, and a header passed with -H is never blanked. The safe
form routes the secret through standard input, which -K - reads as a
configuration file:
printf 'user = "%s:%s"\n' "$API_USER" "$API_PASS" | curl -sS -K - https://api.example.com/securiseprintf 'header = "Authorization: Bearer %s"\n' "$API_TOKEN" | curl -sS -K - https://api.example.com/profilprintf is a shell builtin: no process carries the secret as an argument.
Measured with curl 8.18.0, the server does receive the Authorization header in
both cases. Finally, avoid the name $USER for your variables: the shell
already fills it with your login name, and that is what would be sent to the
server.
Troubleshooting
curl errors fall into two families that are easy to tell apart. The codes in
parentheses (curl: (6), (7), (35)...) are curl errors: the transfer
did not complete, the problem lies on the network, DNS or TLS side. The
4xx codes, on the other hand, mean that the request did arrive but that the
server refused it: the exchange works, it is authentication or permissions
that block. Identifying the family points straight to the right column.
| Problem | Probable cause | Fix |
|---|---|---|
curl: (6) Could not resolve host | DNS or wrong domain name | Check the URL and the network connection |
curl: (7) Failed to connect | Closed port or firewall | Check that the service is listening |
curl: (35) TLS connect error | TLS negotiation impossible (version or cipher suite refused) | Compare the TLS versions accepted on both sides |
curl: (60) SSL certificate ... verify result | Expired, self-signed certificate or unknown authority | Renew the certificate, update ca-certificates, or --cacert; --insecure in dev only |
curl: (60) ... no alternative certificate subject name matches | The URL name is not in the certificate | Check the names (SAN) of the certificate |
curl: (56) Recv failure | Connection interrupted | Retry with -C - to resume |
| HTTP 401 Unauthorized | Invalid token or credentials | Check -u or the Authorization header |
| HTTP 403 Forbidden | Insufficient permissions | Check the access rights |
| HTTP 429 Too Many Requests | Rate limiting | Add a delay between requests |
Key points
curltransfers data over about thirty protocols depending on how it was built (29 on Debian 13, 7 on AlmaLinux 10)- The
-ooption saves to a file,-Okeeps the remote name -Ishows the HTTP headers,-Lfollows redirects- For APIs:
-Xsets the method,-Hadds headers,-dsends data -u user:passfor Basic auth,-H "Authorization: Bearer TOKEN"for tokens-F "file=@chemin"uploads files as multipart--silent --fail --show-errorfor use in scripts- Keep curl up to date: 8.18.0 fixed 6 CVEs, 8.22.0 fixes 9 more
- Pass secrets through a
-Kfile inchmod 600or through standard input (-K -), never as a command argument
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: HTTPie: test an API simply
A new full quiz with random questions
Work again only on the questions you missed
Resources
These links lead to the official documentation of the curl project, the reference to check an option or the list of security advisories.
- Official site: curl.se
- Documentation: curl.se/docs
- Changelog 8.22.0: curl.se/changes.html
- curl CVEs: curl.se/docs/security.html
FAQ: frequently asked questions
The answers below cover authentication, proxies and the security of credentials in scripts.
Definition
curl (Client URL) is a universal CLI data transfer tool supporting 25+ protocols, mainly used for HTTP(S) interactions and REST APIs.| Characteristic | Description |
|---|---|
| Type | Multi-protocol transfer client |
| Protocols | HTTP, HTTPS, FTP, SFTP, SCP, SMTP, POP3, IMAP, LDAP... |
| Main use | REST APIs, downloads, webhooks |
| Authentication | Basic, Digest, Bearer, OAuth, certificates |
| Output format | Plain text, JSON, XML, binary |
| Cross-platform | Linux, macOS, Windows, Docker |
Supported protocols
# HTTP/HTTPS (APIs)
curl https://api.github.com/users/octocat
# FTP (download)
curl ftp://ftp.example.com/fichier.txt -u user:pass
# SMTP (sending email)
curl smtp://smtp.gmail.com:587 --mail-from user@example.com
# LDAP (directory)
curl ldap://ldap.example.com/ou=users,dc=example,dc=com
DevOps use cases
- Healthchecks: check that services are available
- Webhooks: trigger CI/CD (GitLab/GitHub)
- APIs: Terraform, Kubernetes, Vault, Consul
- Monitoring: Prometheus metrics, Loki logs
- Automation: bash scripts, Ansible, Docker
Uses by category
| Category | Use case | Example |
|---|---|---|
| REST APIs | CRUD operations | curl -X POST -d '{"name":"test"}' api.com/users |
| Downloads | Files, releases | curl -LO https://dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl |
| Healthchecks | Service monitoring | curl -f http://app:8080/health || alert |
| Webhooks | CI/CD triggers | curl -X POST -H "Token: xxx" gitlab.com/hook |
| Upload | Artifacts, images | curl -F "file=@build.tar.gz" registry.com/upload |
| HTTP debug | Headers, redirects | curl -vL https://site.com |
Practical examples
# 1. REST API test
curl -H "Accept: application/json" \
-H "Authorization: Bearer TOKEN" \
https://api.example.com/v1/users
# 2. POST JSON
curl -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"secret"}' \
https://api.com/login
# 3. Automatic healthcheck
curl -f -s -o /dev/null http://app:8080/health
if [ $? -eq 0 ]; then echo "OK"; else echo "DOWN"; fi
# 4. Download with resume
curl -C - -O https://releases.ubuntu.com/22.04/ubuntu.iso
# 5. GitLab CI/CD webhook
curl -X POST -F token=TOKEN -F ref=main \
https://gitlab.com/api/v4/projects/123/trigger/pipeline
DevOps: curl = the HTTP Swiss army knife for scripts, CI/CD, K8s, Terraform.General syntax
curl [OPTIONS] [URL]
Fundamental options
| Option | Meaning | Usage |
|---|---|---|
-X METHOD |
HTTP method | -X POST, -X DELETE |
-H "Header" |
Custom header | -H "Content-Type: application/json" |
-d DATA |
POST data | -d "key=value" |
-o FILE |
Output to a file | -o download.zip |
-O |
Remote file name | Keeps the original URL name |
-L |
Follow redirects | Follows 3xx redirects |
-s |
Silent | No progress bar |
-f |
Fail silently | Error code on HTTP 4xx/5xx |
-v |
Verbose | Debug details |
HTTP methods
# GET (default)
curl https://api.com/users
# POST
curl -X POST -d "name=test" https://api.com/users
# PUT
curl -X PUT -d "name=updated" https://api.com/users/1
# DELETE
curl -X DELETE https://api.com/users/1
# PATCH
curl -X PATCH -d "email=new@example.com" https://api.com/users/1
Common combinations
# REST API with auth
curl -H "Authorization: Bearer TOKEN" \
-H "Accept: application/json" \
https://api.example.com/v1/data
# POST JSON
curl -X POST \
-H "Content-Type: application/json" \
-d '{"key":"value"}' \
https://api.com/endpoint
# Download with redirect
curl -LO https://github.com/org/repo/releases/latest/download/file.tar.gz
# Healthcheck script
curl -fs http://localhost:8080/health > /dev/null
# Timeout (30s max)
curl --max-time 30 https://slow-api.com
Basic download
# -O: keeps the remote file name
curl -O https://example.com/fichier.zip
# -o: custom name
curl -o mon_fichier.zip https://example.com/download
Download options
| Option | Effect | Example |
|---|---|---|
-O |
Original URL name | curl -O https://site.com/file.tar.gz |
-o FILE |
Custom name | curl -o backup.tar.gz https://site.com/data |
-C - |
Resume a download | curl -C - -O https://site.com/big.iso |
-L |
Follow redirects | curl -LO https://github.com/org/latest |
--limit-rate |
Speed limit | curl --limit-rate 1M -O https://... |
Practical examples
# Kubernetes kubectl
curl -LO https://dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl
# With progress bar
curl -# -O https://releases.ubuntu.com/22.04/ubuntu.iso
# Resume after an interruption
curl -C - -O https://example.com/big-file.tar.gz
# Bandwidth limit (500 KB/s)
curl --limit-rate 500k -O https://cdn.com/video.mp4
# Multiple files
curl -O https://site.com/file[1-10].txt
With authentication
# HTTP Basic
curl -u user:pass -O https://secure.com/file.zip
# Bearer token
curl -H "Authorization: Bearer TOKEN" \
-O https://api.com/download/file.tar.gz
DevOps: download binaries (Terraform, Helm, kubectl), CI/CD artifacts, GitHub releases.Sending POST data
# Form-urlencoded data (default)
curl -d "name=Jean&email=jean@example.com" https://api.com/users
# JSON
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"Jean","email":"jean@example.com"}' \
https://api.com/users
# From a file
curl -d @data.json -H "Content-Type: application/json" https://api.com/endpoint
Data formats
| Format | Option | Example |
|---|---|---|
| Form URL | -d |
curl -d "key=value" URL |
| JSON | -d + header |
curl -H "Content-Type: application/json" -d '{...}' URL |
| Multipart | -F |
curl -F "field=value" -F "file=@path" URL |
| XML | -d + header |
curl -H "Content-Type: text/xml" -d '<xml>...</xml>' URL |
| File | @file |
curl -d @payload.json URL |
API examples
# Login API
curl -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"secret"}' \
https://api.example.com/auth/login
# GitLab webhook
curl -X POST \
-F "token=PIPELINE_TOKEN" \
-F "ref=main" \
-F "variables[DEPLOY_ENV]=production" \
https://gitlab.com/api/v4/projects/123/trigger/pipeline
# Kubernetes API
curl -X POST \
-H "Authorization: Bearer K8S_TOKEN" \
-H "Content-Type: application/json" \
-d '{"apiVersion":"v1","kind":"Pod",...}' \
https://k8s-api:6443/api/v1/namespaces/default/pods
Tip: -d implies POST automatically (no need for -X POST).The -L option (Location)
# Follows redirects automatically
curl -L https://example.com
# With a download
curl -LO https://github.com/org/repo/releases/latest/download/file.tar.gz
How redirects work
# WITHOUT -L: stops at the 301/302
$ curl https://git.io/short-url
<html><body>You are being redirected...</body></html>
# WITH -L: follows to the final content
$ curl -L https://git.io/short-url
[contenu réel du fichier]
Combined options
| Command | Effect |
|---|---|
curl -L URL |
Basic redirect following |
curl -LI URL |
Final headers after redirects |
curl -Lv URL |
Verbose (see the redirect chain) |
curl -L --max-redirs 5 URL |
At most 5 redirects |
Use cases
# GitHub releases (redirect to the CDN)
curl -LO https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64
# Shortened URLs
curl -L https://bit.ly/example > page.html
# Analyse redirects
curl -Lv https://shortened-url.com 2>&1 | grep '< Location:'
# Limit redirects (security)
curl -L --max-redirs 3 https://suspect-url.com
Debugging redirects
# See every step
curl -Lv https://t.co/shorturl 2>&1 | grep -E '^(>|<) (GET|HTTP|Location)'
# Example output:
> GET /shorturl HTTP/1.1
< HTTP/1.1 301 Moved Permanently
< Location: https://destination.com/page
> GET /page HTTP/1.1
< HTTP/1.1 200 OK
Security: use --max-redirs to avoid infinite loops.Authentication methods
| Type | Syntax | Usage |
|---|---|---|
| HTTP Basic | -u user:pass |
Simple APIs |
| Bearer Token | -H "Authorization: Bearer TOKEN" |
Modern REST APIs |
| API Key | -H "X-API-Key: KEY" |
Cloud services |
| OAuth | -H "Authorization: OAuth ..." |
Third-party applications |
| Certificate | --cert client.pem --key key.pem |
mTLS |
HTTP Basic Auth
# Inline
curl -u admin:password https://api.example.com/users
# Password prompt (secure)
curl -u admin: https://api.example.com/users
# Enter host password for user 'admin':
# From a variable (not $USER: the shell already puts your login there)
curl -u "$API_USER:$API_PASS" https://api.com/endpoint
Bearer Token
# Standard OAuth/JWT
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://api.example.com/v1/data
# From a variable
TOKEN=$(cat ~/.secrets/api-token)
curl -H "Authorization: Bearer $TOKEN" https://api.com/secure
API Key
# Custom header
curl -H "X-API-Key: abc123def456" https://api.service.com/endpoint
# Query parameter
curl "https://api.com/data?api_key=abc123"
Client certificates (mTLS)
# Certificate + key
curl --cert client.crt --key client.key https://secure-api.com
# Combined PEM format
curl --cert client.pem https://api.com
# With a custom CA
curl --cacert ca-bundle.crt --cert client.crt --key client.key https://api.com
DevOps examples
# Kubernetes API
curl --cacert /etc/kubernetes/pki/ca.crt \
--cert /etc/kubernetes/pki/apiserver-kubelet-client.crt \
--key /etc/kubernetes/pki/apiserver-kubelet-client.key \
https://kubernetes.default.svc:6443/api/v1/nodes
# HashiCorp Vault
curl -H "X-Vault-Token: hvs.CAESII..." \
https://vault.example.com:8200/v1/secret/data/myapp
# GitLab API
curl -H "PRIVATE-TOKEN: glpat-xxx" \
https://gitlab.com/api/v4/projects
Security: never credentials in clear text in versioned scripts (use ENV variables).Uploading files
# Multipart form (web upload)
curl -F "file=@image.jpg" https://example.com/upload
# FTP upload
curl -T fichier.txt ftp://ftp.example.com/upload/ -u user:pass
# HTTP PUT
curl -X PUT --data-binary @backup.tar.gz https://storage.com/backups/
Upload methods
| Protocol | Syntax | Usage |
|---|---|---|
| Multipart | -F "file=@path" |
Web forms, APIs |
| FTP/SFTP | -T file |
File servers |
| PUT | -X PUT --data-binary @file |
REST APIs, S3 |
| POST | -d @file |
Webhooks, APIs |
Practical examples
# Upload an image to an API
curl -F "photo=@profile.jpg" \
-F "description=Mon avatar" \
-H "Authorization: Bearer TOKEN" \
https://api.example.com/users/me/avatar
# FTP with structure
curl -T backup-$(date +%F).tar.gz \
ftp://backup.example.com/archives/ \
-u backupuser:password
# S3-compatible (MinIO/Wasabi)
curl -X PUT \
-H "x-amz-acl: private" \
--data-binary @file.zip \
https://bucket.s3.amazonaws.com/path/file.zip
# Multiple files
curl -F "file1=@doc.pdf" \
-F "file2=@image.png" \
https://api.com/batch-upload
Upload with progress
# Progress bar
curl -# -F "file=@large-video.mp4" https://cdn.com/upload
# Detailed progress
curl -F "file=@data.tar.gz" \
--progress-bar \
https://storage.com/upload | tee /dev/null
CI/CD artifacts
# Nexus Repository
curl -u admin:admin123 \
--upload-file target/app-1.0.jar \
https://nexus.example.com/repository/maven-releases/com/app/1.0/
# Artifactory
curl -H "X-JFrog-Art-Api: TOKEN" \
-T build/release.tar.gz \
https://artifactory.com/artifactory/libs-release-local/app/
Limit: some servers cap the size (check Content-Length).Proxy configuration
# HTTP proxy
curl -x http://proxy.local:3128 https://example.com
# With authentication
curl -x http://user:pass@proxy.local:8080 https://api.com
# SOCKS5 proxy
curl -x socks5://localhost:1080 https://example.com
Environment variables
# Configure a global proxy
export http_proxy=http://proxy.company.com:8080
export https_proxy=http://proxy.company.com:8080
export no_proxy=localhost,127.0.0.1,.local
# curl uses it automatically
curl https://api.example.com
Proxy types
| Type | Syntax | Usual port |
|---|---|---|
| HTTP | http://proxy:port |
3128, 8080 |
| HTTPS | https://proxy:port |
3128 |
| SOCKS4 | socks4://proxy:port |
1080 |
| SOCKS5 | socks5://proxy:port |
1080 |
http:// proxy and 443 for an https:// proxy, not Squid's 3128.Exceptions (no_proxy)
# Bypass the proxy for some domains
curl -x http://proxy:8080 \
--noproxy localhost,192.168.0.0/16,.internal \
https://internal-api.company.local
# Environment variable
export no_proxy="localhost,127.0.0.1,.local,.internal,10.0.0.0/8"
Corporate use cases
# Corporate proxy with auth
curl -x http://employee:password@proxy.corp.com:8080 \
https://registry.hub.docker.com/v2/
# SSH tunnel as a SOCKS proxy
ssh -D 1080 -N bastion.company.com &
curl -x socks5://localhost:1080 https://internal-app.local
# Proxy for a Docker registry
curl -x $HTTP_PROXY \
https://registry.example.com/v2/_catalog
Proxy debugging
# Verbose to see the proxy connection
curl -vx http://proxy:8080 https://example.com 2>&1 | grep -i proxy
# Test proxy connectivity
curl -x http://proxy:8080 -I https://www.google.com
~/.curlrc config: add proxy = http://proxy:8080 for a permanent proxy.The -I option (HEAD request)
# Headers only
curl -I https://example.com
# Example output:
HTTP/2 200
server: nginx
content-type: text/html
content-length: 1256
last-modified: Wed, 08 Jan 2026 10:30:00 GMT
etag: "abc123"
Ways to show headers
| Command | Effect |
|---|---|
curl -I URL |
HEAD request (headers only) |
curl -i URL |
Headers + body |
curl -v URL |
Headers + full debug |
curl -D headers.txt URL |
Saves the headers to a file |
Use cases
# Check the HTTP code
curl -I -s https://api.com/health | head -1
# HTTP/2 200
# Extract a specific header
curl -I https://example.com 2>/dev/null | grep -i content-type
# content-type: application/json
# Test redirects
curl -IL https://short.url | grep -E '^(HTTP|Location):'
# Check the cache
curl -I https://cdn.com/image.jpg | grep -i cache-control
With authentication
# API with a Bearer token
curl -I -H "Authorization: Bearer TOKEN" https://api.com/endpoint
# Basic auth
curl -I -u user:pass https://secure.example.com/resource
Custom headers
# Add headers to the HEAD request
curl -I \
-H "Accept: application/json" \
-H "User-Agent: MyScript/1.0" \
https://api.example.com
Monitoring scripts
# Healthcheck with the HTTP code
HTTP_CODE=$(curl -I -s -o /dev/null -w '%{http_code}' https://app.com/health)
if [ "$HTTP_CODE" = "200" ]; then
echo "OK"
else
echo "FAIL: $HTTP_CODE"
fi
# Extract the ETag for the cache
ETAG=$(curl -I -s https://cdn.com/app.js | grep -i etag | cut -d' ' -f2)
echo "Current ETag: $ETAG"
Difference: -I = HEAD request (no body), -i = GET with the headers shown.Verbose mode (-v)
# Full request/response debug
curl -v https://api.example.com
# Example output:
* Trying 93.184.216.34:443...
* Connected to example.com (93.184.216.34) port 443
> GET /api HTTP/1.1
> Host: api.example.com
> User-Agent: curl/7.68.0
> Accept: */*
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 234
{"status":"ok"}
Verbose symbols
| Symbol | Meaning |
|---|---|
* |
Connection information |
> |
Headers SENT |
< |
Headers RECEIVED |
} |
Data sent |
{ |
Data received |
Debug levels
# Standard verbose
curl -v https://api.com
# Full trace (even more detailed)
curl --trace trace.txt https://api.com
# Readable ASCII trace
curl --trace-ascii trace-ascii.txt https://api.com
Debug use cases
# Check the headers sent
curl -v -H "Authorization: Bearer TOKEN" https://api.com 2>&1 | grep '^>'
# Analyse the SSL negotiation
curl -v https://secure-api.com 2>&1 | grep -E 'SSL|TLS'
# Identify redirects
curl -vL https://short.url 2>&1 | grep -E '^(>|<) (GET|HTTP|Location)'
# Debug a timeout
curl -v --connect-timeout 5 https://slow-api.com
Redirecting stderr
# Verbose to a file (stdout = response)
curl -v https://api.com 2> debug.log > response.json
# Analyse only the verbose output
curl -v https://api.com 2>&1 >/dev/null | grep '^[*><]'
Production examples
# Debug a failing webhook
curl -v -X POST \
-H "Content-Type: application/json" \
-d '{"event":"deploy"}' \
https://webhook.example.com/trigger 2>&1 | tee webhook-debug.log
# Check the SSL certificate
curl -v https://expired-cert.com 2>&1 | grep -A 5 'SSL certificate'
# Analyse performance
curl -v -w '\nTime: %{time_total}s\n' https://api.com
Silent alternative
# Opposite of -v: fully silent mode
curl -s https://api.com # Pas de progress, pas de verbose
# Silent with fail
curl -sf https://api.com/health || echo "API DOWN"
Usage: -v is essential to debug APIs, SSL, auth, timeouts.The --limit-rate option
# Limit in kilobytes/sec
curl --limit-rate 100k -O https://example.com/film.mp4
# Limit in megabytes/sec
curl --limit-rate 2M -O https://cdn.com/ubuntu.iso
Supported units
| Unit | Meaning | Example |
|---|---|---|
k or K |
Kilobytes/s | --limit-rate 500k = 500 KB/s |
m or M |
Megabytes/s | --limit-rate 5M = 5 MB/s |
g or G |
Gigabytes/s | --limit-rate 1G = 1 GB/s |
| (number alone) | Bytes/s | --limit-rate 51200 = 50 KB/s |
Use cases
# Backup without saturating the network
curl --limit-rate 1M -O https://backup.com/database-dump.tar.gz
# Download during office hours (low priority)
curl --limit-rate 200k -C - -O https://releases.ubuntu.com/22.04/ubuntu.iso
# Multiple limited files
for file in file{1..10}.zip; do
curl --limit-rate 500k -O https://cdn.com/$file
done
With progress
# See the real speed
curl --limit-rate 1M -# -O https://example.com/large-file.tar.gz
# Detailed progress
curl --limit-rate 500k -O https://cdn.com/video.mp4
# Output montre ~500 Ko/s max
Dynamic script
#!/bin/bash
# Limite selon heure
heure=$(date +%H)
if [ $heure -ge 9 ] && [ $heure -le 18 ]; then
# Heures bureau : 500 Ko/s
rate="500k"
else
# Nuit : illimité
rate="100M"
fi
curl --limit-rate $rate -O https://backup.com/file.tar.gz
Combining options
# Limit + resume + redirect
curl --limit-rate 1M -C - -LO https://releases.ubuntu.com/22.04/ubuntu.iso
# Limit + timeout
curl --limit-rate 500k --max-time 3600 -O https://cdn.com/big.zip
Note: the limit applies on average (it can fluctuate slightly).Saving and reusing
# Save cookies
curl -c cookies.txt https://app.com/login
# Reuse cookies
curl -b cookies.txt https://app.com/dashboard
# Both (save + load)
curl -b cookies.txt -c cookies.txt https://app.com/page
Cookie options
| Option | Effect | Example |
|---|---|---|
-c FILE |
Write cookies → file | curl -c jar.txt URL |
-b FILE |
Read cookies ← file | curl -b jar.txt URL |
-b "NAME=value" |
Inline cookie | curl -b "session=abc123" URL |
-j/--junk-session-cookies |
Ignore session cookies | Clean restart |
Session workflow
# 1. Login (gets the session cookie)
curl -c cookies.txt \
-d "username=admin&password=secret" \
https://app.com/login
# 2. Authenticated actions
curl -b cookies.txt https://app.com/api/users
curl -b cookies.txt https://app.com/api/settings
# 3. Logout
curl -b cookies.txt -c cookies.txt https://app.com/logout
Cookie file format (Netscape)
# Netscape HTTP Cookie File
.example.com TRUE / FALSE 1735689600 session_id abc123def456
.example.com TRUE / FALSE 0 user_pref theme=dark
Inline cookies
# Single cookie
curl -b "session=xyz789" https://api.com/data
# Multiple cookies
curl -b "session=xyz; user_id=42; theme=dark" https://app.com
# Manual header (equivalent)
curl -H "Cookie: session=xyz; user_id=42" https://app.com
DevOps use cases
# Jenkins API with a crumb
CRUMB=$(curl -c jenkins-cookies.txt -s \
'http://jenkins:8080/crumbIssuer/api/json' \
| jq -r '.crumb')
curl -b jenkins-cookies.txt \
-H "Jenkins-Crumb: $CRUMB" \
-X POST http://jenkins:8080/job/myjob/build
# Web application with CSRF
curl -c cookies.txt https://app.com/form
CSRF=$(grep csrf_token cookies.txt | cut -f7)
curl -b cookies.txt -d "csrf=$CSRF&data=value" https://app.com/submit
Cookies + redirects
# Follow redirects with cookies
curl -L -b cookies.txt -c cookies.txt https://app.com/login-redirect
Security: protect cookie files (chmod 600 cookies.txt), they contain active sessions.Security good practices
# ❌ NEVER in clear text in scripts
curl -u admin:P@ssw0rd https://api.com # DANGER!
# Correct: environment variables
export API_USER="admin"
export API_PASS="secret"
curl -u "$API_USER:$API_PASS" https://api.com
# Correct: protected credentials file
chmod 600 ~/.api-credentials
source ~/.api-credentials # Export USER/PASS
curl -u "$API_USER:$API_PASS" https://api.com
Secure methods
| Method | Security | Usage |
|---|---|---|
| .env file | Medium | source .env && curl -u "$API_USER:$API_PASS" |
| Interactive prompt | Good | read -sp "Password: " API_PASS |
| Vault (Hashicorp) | Very good | vault kv get secret/api |
| Secrets Manager | Very good | AWS/Azure/GCP Secrets |
| Short-lived tokens | Good | OAuth, JWT with expiry |
Secure prompt
#!/bin/bash
# Demander mot de passe sans écho
read -sp "API Password: " API_PASS
echo
curl -u "admin:$API_PASS" https://api.example.com/data
# Nettoyer variable
unset API_PASS
Credentials file
# ~/.api-credentials (chmod 600)
export API_TOKEN="eyJhbGciOiJIUzI1NiIs..."
export DB_PASSWORD="secret123"
# Script
#!/bin/bash
set -a # Auto-export
source ~/.api-credentials
set +a
curl -H "Authorization: Bearer $API_TOKEN" https://api.com
HashiCorp Vault
# Fetch the secret dynamically
API_TOKEN=$(vault kv get -field=token secret/api/production)
curl -H "Authorization: Bearer $API_TOKEN" \
https://api.example.com/secure
# Clean up
unset API_TOKEN
Cloud secrets
# AWS Secrets Manager
API_KEY=$(aws secretsmanager get-secret-value \
--secret-id prod/api/key \
--query SecretString --output text)
curl -H "X-API-Key: $API_KEY" https://api.com
# Azure Key Vault
TOKEN=$(az keyvault secret show \
--vault-name myVault \
--name api-token \
--query value -o tsv)
curl -H "Authorization: Bearer $TOKEN" https://api.com
Secure CI/CD
# GitLab CI (.gitlab-ci.yml)
deploy:
script:
- curl -H "Authorization: Bearer $API_TOKEN" https://api.com/deploy
variables:
API_TOKEN: $CI_API_TOKEN # Variable protégée GitLab
# GitHub Actions: the secret goes through env, never interpolated in run
- name: Deploy
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: |
curl -H "Authorization: Bearer $API_TOKEN" \
https://api.example.com/deploy
Security checklist
- Never credentials versioned in Git
.envin.gitignore- Short-lived tokens (rotation)
- ENV variables in CI/CD
unsetafter use- No
curl -vin CI logs: it prints theAuthorizationheader in clear text
The --insecure option (-k)
# Accepts invalid SSL certificates
curl --insecure https://self-signed.example.com
# Short form
curl -k https://expired-cert.local
What --insecure turns off
| Check | Normal | With --insecure |
|---|---|---|
| Certificate validity | Checked | Ignored |
| Expiry | Checked | Ignored |
| Authority (CA) | Checked | Ignored |
| Hostname match | Checked | Ignored |
| Revocation | Not checked by default (--crlfile, --cert-status) |
Ignored |
Legitimate use cases
# 1. Local development (self-signed certificate)
curl -k https://localhost:8443/api/test
# 2. Internal test environment
curl -k https://dev-api.internal.local/health
# 3. Debug an SSL problem
curl -kv https://problematic-site.com 2>&1 | grep -i ssl
Security risks
Dangers:- Vulnerable to Man-in-the-Middle (MITM)
- Impossible to check the server identity
- Traffic potentially intercepted
- Expired/revoked certificates accepted
Secure alternatives
# 1. Custom CA certificate (RECOMMENDED)
curl --cacert /path/to/ca-bundle.crt https://internal-api.com
# 2. Add the CA to the system
sudo cp ca-cert.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
curl https://internal-api.com # Fonctionne sans --insecure
# 3. Environment variable
export CURL_CA_BUNDLE=/etc/ssl/certs/custom-ca.pem
curl https://internal-api.com
# 4. ~/.curlrc config file
echo "cacert = /etc/ssl/certs/ca-bundle.crt" >> ~/.curlrc
Development workflow
# Local dev: generate a self-signed certificate
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout key.pem -out cert.pem -days 365 \
-subj "/CN=localhost"
# Test server (Python 3.13 http.server does not handle TLS)
openssl s_server -accept 8443 -cert cert.pem -key key.pem -www
# Client with a custom CA
curl --cacert cert.pem https://localhost:8443
Production: NEVER --insecure
# ❌ DANGEROUS in production
curl -k https://api.bank.com/transfer # VULNÉRABLE MITM!
# Correct
curl https://api.bank.com/transfer # Validation SSL complète
Detection in scripts
# Audit scripts for --insecure
grep -r "curl.*--insecure\|curl.*-k" /scripts/
# CI/CD: block --insecure
if grep -q "curl.*-k" deploy.sh; then
echo "ERROR: --insecure detected!"
exit 1
fi
Rule: --insecure = NEVER in production, only local dev/debug.