Geonode logo
Geonode Team

Geonode Team

Updated: September 2, 2026

Published: 2026-09-02

curl for Beginners: A Complete Guide

curl fetches a URL and prints the result. Everything else is options, and there are over two hundred of them. You need about eight to be productive. This guide covers those eight, the mental model that makes the rest readable, and the handful of mistakes that catch everyone at the start. By the end you will be able to send requests, read responses, debug what actually went over the wire, and know which manual page to open next.

A quick note on who is writing this. We are Geonode and we sell proxies, so curl is the tool we most often ask people to use when diagnosing something. The honest framing for a beginner: curl is not a proxy tool and you do not need a proxy to learn it. Everything below works against public endpoints from your own connection, for free. Proxies become relevant much later, when you are making enough requests that a target starts rate-limiting you, or when you need to see what a page looks like from another country. Neither is a beginner problem. Learn the tool first.

What curl Is and What It Is For

curl is a command-line program for transferring data using URLs. Its own manual describes it as supporting "DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS" — though in practice almost everyone uses it for HTTP and HTTPS.

What it is good for:

  • Calling an API from a terminal or a script
  • Checking whether a URL works, and what it returns
  • Seeing exactly what a server sends back, headers and all
  • Downloading files
  • Debugging: reproducing a request outside your application to find out whether the problem is your code or the server

What it is not: a browser. It does not execute JavaScript, it does not render anything, and it does not maintain a session unless you tell it to. A page that looks full in a browser may return a nearly empty skeleton to curl, and that is expected rather than broken.

Your First Requests

curl https://example.com

That performs a GET and prints the response body to your terminal. If the output is a wall of HTML, curl is working.

Four immediately useful variations:

See the headers as well as the body with -i, documented as -i, --show-headers: "Show response headers in the output."

curl -i https://example.com

Save to a file with -o (your chosen name) or -O (the remote name):

curl -o page.html https://example.com
curl -O https://example.com/file.zip

Follow redirects with -L: "Follow HTTP redirects and repeat requests with the method originally specified." Without it, curl stops at the first redirect and shows you the redirect page rather than the destination.

curl -L https://example.com

Be quiet but still report errors with -sS. -s suppresses the progress meter, -S keeps error messages. Together they are what you want in any script.

curl -sS https://example.com

If you remember one line from this article, make it this one:

curl -sSL https://example.com

Reading the Response

Beginners often stare at a body when the answer is in the headers.

curl -i https://example.com
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 1256

The first line is the status. 200 means success. 301 and 302 are redirects — add -L. 401 and 403 mean you are not permitted. 404 means it does not exist. 429 means you are going too fast. 500 and above mean the server has a problem.

content-type tells you what you actually received, and it answers a lot of confusion. If you called an API expecting JSON and see text/html, you got an error page or a login redirect, and the parse error you are about to hit is a symptom rather than the cause.

To get the headers without the body:

curl -sS -o /dev/null -D - https://example.com

That does a normal GET, throws the body away, and prints the headers. It is more reliable than -I, which sends a HEAD request and can behave differently — a distinction covered in our guide to curl HEAD requests.

For a summary rather than raw headers, -w prints selected values:

curl -sS -o /dev/null -w 'status=%{response_code} time=%{time_total}s\n' https://example.com

More on reading headers properly in showing response headers with curl.

Sending Data

The other half of the job.

A POST with form data:

curl -d "name=Ada&role=engineer" https://api.example.com/users

Using -d implies POST and sets Content-Type: application/x-www-form-urlencoded.

A POST with JSON — and this is the single most common beginner mistake, because -d alone does not set a JSON content type:

curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada","role":"engineer"}'

Forget that header and many APIs return 415 Unsupported Media Type, which is a confusing error until you know it refers to your Content-Type rather than your data. We wrote about that specific error in what is a 415 status code.

Data from a file, using @ to mean "read this file":

curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d @payload.json

A GET with query parameters built from key-value pairs, using -G:

curl -G https://api.example.com/search -d "q=proxy" -d "limit=10"

Other methods with -X. Use it only for methods that have no dedicated option — PUT, DELETE, PATCH. Note the manual's warning that -X "only changes the actual word used in the HTTP request, it does not alter the way curl behaves", which is why -X HEAD does not work and -I exists.

Headers, Authentication and Cookies

Custom headers with -H, repeatable:

curl -H "Authorization: Bearer eyJhbG..." \
     -H "Accept: application/json" \
     https://api.example.com/me

Basic authentication with -u:

curl -u username:password https://api.example.com/private

Omit the password and curl prompts for it, which keeps it out of your shell history:

curl -u username https://api.example.com/private

A user agent with -A, since curl identifies itself as curl by default and some servers respond differently:

curl -A "Mozilla/5.0 (compatible; MyBot/1.0; +https://example.com/bot)" https://example.com

If you are writing an automated client, an honest user agent with a contact URL is both good manners and a practical advantage — anonymous automation is blocked far more readily than identified automation.

Cookies. curl does not keep them between invocations unless you ask:

curl -c cookies.txt -d "user=ada&pass=secret" https://example.com/login
curl -b cookies.txt https://example.com/dashboard

-c writes a cookie jar, -b reads one. This is how you handle anything requiring a session.

Seeing What Actually Went Over the Wire

The habit that separates people who debug quickly from people who guess.

curl -v https://example.com

The manual explains the prefixes: "> header sent by curl, < header received by curl, } data sent by curl, { data received by curl, * additional info provided by curl."

To see only what you sent:

curl -v https://example.com 2>&1 | grep '^>'

This resolves an entire category of confusion, because the header you set in code is not always the header that was transmitted. Libraries add defaults, override values and reorder things. When a server "ignores" your header, check first whether you actually sent it.

Verbose output goes to stderr, which is why 2>&1 is needed before piping — deliberate, so the body stays clean on stdout.

A warning the manual gives and worth repeating: verbose and trace output "might contain sensitive data, including usernames, credentials or secret data content". Redact before pasting into a ticket.

The Flags Worth Memorising

Everything above condenses to a small set.

FlagDoes
-iShow response headers with the body
-o file / -OSave to a named file / the remote name
-LFollow redirects
-sSQuiet, but still report errors
-HAdd a header
-dSend data (implies POST)
-uBasic authentication
-vShow the full exchange
--failTreat HTTP errors as failures
-m / --connect-timeoutTime limits

The last two are the ones beginners skip and later regret.

--fail matters because curl treats a 404 as a successful transfer by default — it downloads the error page and exits 0. In a script, that means you save an HTML error page under the name installer.dmg and carry on. --fail makes HTTP errors produce a non-zero exit code and no output.

Timeouts matter because curl has no overall time limit by default. A hung request hangs your script indefinitely. --connect-timeout 5 -m 30 bounds it. There is more on this in setting a timeout with curl.

The line worth putting in every script:

curl --fail --silent --show-error --location --connect-timeout 5 --max-time 30 "$URL"

A Worked Example From Start to Finish

Putting the pieces together on a realistic task: calling a public API, checking it worked, and handling the failure case.

Step one — see what the endpoint returns. Start with headers, not the body:

curl -sS -o /dev/null -D - https://api.github.com/repos/curl/curl

You get a status line and headers. If the status is 200 and content-type says JSON, you are talking to the right thing.

Step two — look at the body, formatted. Raw JSON on one line is unreadable, so pipe it through jq:

curl -sS https://api.github.com/repos/curl/curl | jq '{name, stargazers_count, language}'

If jq is not installed, python3 -m json.tool does the formatting job with no extra dependency.

Step three — check what you sent. If something is not behaving, look at the request rather than guessing:

curl -v https://api.github.com/repos/curl/curl 2>&1 | grep '^>'

Step four — make it safe for a script. Add failure handling and time limits, and capture the status separately from the body:

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

URL="https://api.github.com/repos/curl/curl"
BODY=$(mktemp)

STATUS=$(curl --silent --show-error --location \
              --connect-timeout 5 --max-time 30 \
              --write-out '%{response_code}' --output "$BODY" \
              "$URL")

case "$STATUS" in
  200) jq -r '.stargazers_count' < "$BODY" ;;
  404) echo "not found" >&2; exit 1 ;;
  429) echo "rate limited, retry after: $(date)" >&2; exit 1 ;;
  *)   echo "unexpected status $STATUS" >&2; head -c 200 "$BODY" >&2; exit 1 ;;
esac

rm -f "$BODY"

Three things in there are worth carrying into everything you write. --write-out '%{response_code}' with --output separates the status from the body so you can branch on it. Printing the first 200 characters of the body on an unexpected status turns a mystery into a readable error. And --connect-timeout with --max-time means the script finishes even when the network does not.

Step five — respect the rate limit. Public APIs publish their limits in headers. Reading them costs nothing and prevents the most common way of getting blocked:

curl -sS -o /dev/null -D - https://api.github.com/repos/curl/curl | grep -i ratelimit

Common Beginner Mistakes

Forgetting -L. You get a short response containing a redirect notice and conclude the URL is broken. It is not.

Forgetting --fail in scripts. A 404 becomes a saved error page and an exit code of zero. Silent, and expensive later.

Using -d with JSON without setting Content-Type. Produces 415 or a confusing parse error at the server.

Shell quoting. Single quotes preserve everything literally; double quotes let the shell expand $ and backticks. For a JSON body containing double quotes, wrap it in single quotes. If your data contains single quotes as well, put it in a file and use -d @file.json.

Assuming curl sees what a browser sees. curl does not run JavaScript. A near-empty response from a page that looks full in a browser means the content is rendered client-side, and curl is behaving correctly.

Ignoring the status code. A body that says "error" with a 200 status and a body that says "error" with a 500 status are different problems. Read both.

Putting credentials in the command line. They land in shell history and are visible in the process list to other users on the machine. Use -u user and let curl prompt, or read from an environment variable.

Disabling certificate verification to make something work. -k silences a warning that was telling you something. Find out what, first.

People Also Ask

What is curl used for?

Transferring data over URLs from a command line or script — calling APIs, checking what a server returns, downloading files, and reproducing a request outside an application to isolate a problem. It supports many protocols but is used overwhelmingly for HTTP and HTTPS.

How do I make a GET request with curl?

curl https://example.com. GET is the default, so no flag is needed. Add -L to follow redirects and -i to see the response headers alongside the body.

How do I send JSON with curl?

curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' URL. The header is essential — -d on its own sends form-encoded content type, and APIs expecting JSON will typically reject it with a 415.

Why does curl return nothing?

Several possibilities: the response body is genuinely empty, you followed a redirect you did not follow (add -L), the content is rendered by JavaScript that curl does not execute, or the request failed and you did not see the error because of -s without -S. Run with -i to see the status code.

What is the difference between -o and -O in curl?

-o filename saves to a name you choose. -O saves under the filename from the URL, discarding the path. Use -o when the URL has no useful filename or when you need a specific name.

How do I see the request curl sends?

curl -v URL and look for lines beginning with >. Verbose output goes to stderr, so add 2>&1 before piping. This is the fastest way to confirm that a header you configured actually made it onto the wire.

Does curl follow redirects by default?

No. Add -L. This is the most common reason a beginner's curl command returns a short unexpected response — you are seeing the redirect, not the destination.

Do I need a proxy to use curl?

No. curl works fine against public endpoints from your own connection. Proxies become relevant only when you are making enough requests to be rate-limited, or when you need to see what a site serves in another country. Neither is a reason to buy anything while learning.

Wrapping Up

curl has an intimidating number of options and a very small useful core. -i to see headers, -L to follow redirects, -o to save, -H to add headers, -d to send data, -u for authentication, -v to see what happened, and --fail plus a timeout for anything running unattended. That is the whole working set for most people.

The two habits that matter more than any flag: read the status code and the Content-Type before reading the body, because they usually name the problem outright; and use -v to check what you actually sent rather than what you meant to send, because the difference between those two is where a surprising share of bugs live.

Everything past that is the manual, which is long, authoritative and worth dipping into whenever you find yourself writing a workaround. The option you want usually exists.

curl for Beginners: Syntax First Requests and Common Mistakes | Geonode