kmail.at
contact

elsewhere

EnglishEN
← learning

networking · difficulty ◆◆

curl — transfer data with URLs

The terminal's web browser — and its API client, downloader, and debugger.

Every API you have ever called from a browser was probably tested first with curl — the tool that has shipped with every Unix-like system since the late 1990s and still has no real rival for talking to a server from a terminal.

AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.

2026-09-10 · 6 min read

$ curl

What it does

curl transfers data to or from a server using a URL. It supports HTTP, HTTPS, FTP, SFTP, and a dozen other protocols, but in practice you use it for one thing: making HTTP requests from the command line. Give it a URL and it fetches the response and prints it to your terminal. Give it -X POST and a body and it sends data instead. It is the Swiss-army knife of talking to servers.

Why it matters

curl is how you test an API without opening a browser, download a file without a GUI, check whether a server is actually responding, and debug what a web request really sends and receives. When a website 'doesn't work', curl is the tool that tells you whether the problem is the network, the server, or the app. And because it is scriptable and present on every Unix box, it is the backbone of countless shell scripts and CI pipelines.

Exit codes, and the -f flag your scripts want

curl's exit code describes the transfer, not the page: a 404 is a perfectly successful transfer, so `curl -s -o /dev/null -w '%{http_code}'` prints 404 while the shell still sees 0. Add -f (--fail) when a bad HTTP status should be an error, and -S alongside -s so the error text is not suppressed along with the progress bar. That is the pair to put in a cron job or a CI step: a missing page then fails the command instead of silently succeeding with an error page in the output.

Example

$ curl -s -o /dev/null -w "HTTP %{http_code} | %{size_download} bytes | %{time_total}s\n" https://example.com
HTTP 200 | 577 bytes | 0.096898s

-s silences the progress bar, -o /dev/null discards the body, and -w prints a custom summary. This is the canonical 'is this site up and how fast?' one-liner. (Verified live on this box: 577 bytes in 0.0969 s — the byte count follows example.com's current page and the time changes on every run.)

$ curl -s https://api.github.com/repos/curl/curl | head -n 7
{
  "id": 569041,
  "node_id": "MDEwOlJlcG9zaXRvcnk1NjkwNDE=",
  "name": "curl",
  "full_name": "curl/curl",
  "private": false,
  "owner": {

A GET request to a JSON API. curl prints the raw response body — here the first seven lines of GitHub's metadata for the curl/curl repository (id 569041). Pipe the whole response into jq to pretty-print it and pull out fields you actually want.

$ curl -s -o /dev/null -w "%{http_code}\n" -X POST -H "Content-Type: application/json" -d '{"name":"test"}' https://httpbin.org/post
200

-X POST sends a POST request, -H adds a header, -d sends a JSON body. The -w '%{http_code}' prints just the status code, which is how scripts check whether an API call succeeded.

$ curl -s -I https://example.com
HTTP/2 200 
date: Thu, 15 Oct 2026 10:30:09 GMT
content-type: text/html; charset=utf-8
server: cloudflare
last-modified: Tue, 13 Oct 2026 22:05:48 GMT
allow: GET, HEAD
accept-ranges: bytes
age: 6
cf-cache-status: HIT
cf-ray: a4ae259848d528d0-VIE
alt-svc: h3=":443"; ma=86400

-I sends a HEAD request, which fetches only the response headers, not the body. This is the fastest way to inspect a server's headers, caching, and content type. (Verified live: date, age, cf-cache-status and cf-ray are generated per request, so yours will differ.)

$ curl -sS -f https://example.com/404test; echo exit=$?
curl: (22) The requested URL returned error: 404
exit=22

Without -f the same URL exits 0 — curl reports that the transfer worked, not that the page exists. With -f, an HTTP error becomes exit code 22. (Verified live on this box; the bare `-s -o /dev/null -w '%{http_code}'` version prints 404 and still exits 0, which is exactly the trap -f closes.)

Common flags

-s
silent — suppress the progress bar and error messages (essential for scripts)
-o FILE
write the response body to a file instead of stdout (use -o /dev/null to discard it)
-O
save the body to a file named after the URL's last path segment (like wget)
-X METHOD
set the HTTP method explicitly, e.g. -X POST, -X PUT, -X DELETE
-H HEADER
add a request header, e.g. -H "Content-Type: application/json"
-d DATA
send data in the request body (implies POST); use -d @file.json to read from a file
-I
send a HEAD request — fetch only the response headers
-w FORMAT
print a custom summary after the transfer, e.g. -w "%{http_code} %{time_total}s"

History

Origin

curl's line starts in November 1996, when the Brazilian developer Rafael Sagula released HttpGet 0.1. Daniel Stenberg was writing an IRC bot and wanted currency rates fetched from a web page automatically; he fixed a couple of bugs in HttpGet, sent the fixes back, and within a release or two had taken over maintenance. When FTP support arrived the name was wrong, so it became urlget 2.0 in August 1997; when uploads arrived that name was wrong too, and on 20 March 1998 Stenberg shipped curl 4.0, carrying the old version numbers over. He still leads the project today.

The name and the reach

The name was Stenberg's choice: the word contains 'URL', it can be read as 'see URL' — which is what the tool does, show you what is at a URL — and it is a real English word, so it is pronounced 'curl'. The reach came later and steadily: curl was in Linux distributions and the BSD ports collections by the early 2000s, was bundled with Mac OS X 10.1 in 2001 and with Windows 10 in 2018, and the libcurl library underneath is embedded in countless applications, package managers, and devices.

Fun facts

Pros & cons

pros

  • + Universal — ships with every Unix-like system, macOS, and modern Windows, no install needed
  • + Handles HTTP, HTTPS, FTP, and a dozen more protocols with one consistent interface
  • + Scriptable and pipe-friendly: -s, -o, and -w make it perfect for CI and shell automation
  • + Full control over headers, methods, bodies, and redirects — nothing hidden from you

cons

  • − Output is raw by default — JSON and HTML come out unformatted until you pipe through jq or a pager
  • − Does not follow redirects unless you remember -L, which trips up beginners
  • − The flag set is huge and the syntax can feel cryptic compared to a GUI browser

Takeaways

  1. 1Check a site is up and fast: `curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" URL`
  2. 2Download a file: `curl -O URL` saves it with the server's filename
  3. 3Follow redirects with -L or many links will silently fail
  4. 4Test an API: `curl -X POST -H "Content-Type: application/json" -d @payload.json URL`
  5. 5Inspect headers only with -I — the fastest way to see a server's response metadata
  6. 6Pipe JSON into jq for readable output: `curl -s URL | jq .`

Related commands