cURL is the command-line workhorse for moving data over the network. It speaks HTTP, HTTPS, and a long list of other protocols, and it ships on almost every machine you will touch, so it is the first thing most engineers reach for when they need to inspect an endpoint, test an API, or pull down a page. The GET method sits at the center of all of that: it is how you ask a server for a resource and read what comes back.

This guide is a focused walkthrough on how to send GET requests with cURL. You will start from the bare command, then layer on query parameters, request headers, redirects, JSON handling, and the flags that let you see exactly what the server sent. By the end you will know how to drive cURL well enough for real debugging and small scraping jobs, and where a raw GET stops being enough.

What a GET request actually does

GET is the simplest and most common HTTP method. When you open a URL in a browser, the browser sends a GET request to the server, and the server replies with the resource: HTML, an image, a JSON payload, whatever lives at that address. GET only reads. It does not send a body to create or modify anything the way POST or PUT do, which makes it the right method for fetching pages, calling read-only API endpoints, and pulling static assets.

cURL defaults to GET, so you never have to name the method for a plain fetch. The shortest possible request is the program name followed by a URL:

bash
curl https://httpbin.org/get

That prints the response body to your terminal. httpbin.org is a free request-inspection service that echoes back whatever it receives, which makes it ideal for learning: every example below uses it so you can run the commands and see exactly what cURL sent. If you ever want to be explicit about the method, -X GET or --request GET spells it out, but for a normal GET it is redundant.

Passing query string parameters

Most real endpoints expect parameters. A query string is the part of a URL after the ?, written as key=value pairs joined by &. The most direct way to send them is to put them straight in the URL, quoted so your shell does not try to interpret the & as a background job:

bash
curl 'https://httpbin.org/get?city=Berlin&page=2'

The single quotes matter. Without them the shell sees the & and splits the command, so always quote any URL that contains &, ?, or spaces.

cURL can also assemble the query string for you with -G and -d. The -d flag normally builds a POST body, but adding -G tells cURL to take that data and append it to the URL as a GET query string instead:

bash
curl -G \
  -d "city=Berlin" \
  -d "page=2" \
  https://httpbin.org/get

Both commands produce the same request. The -G form reads better when you have several parameters or are generating them in a script, since each pair is its own flag and you do not have to hand-build the ?key=value&key=value string.

URL-encoding parameter values

Plain -d does not encode anything, so a value with a space, an ampersand, or a slash in it will break the query string. When the value is not already safe, use --data-urlencode together with -G. cURL encodes the value for you and still appends it as a query parameter:

bash
curl -G \
  --data-urlencode "q=web scraping & data" \
  --data-urlencode "sort=date desc" \
  https://httpbin.org/get

In the echoed response you will see the spaces turned into %20 and the ampersand into %26, which is exactly what a server expects. Reach for --data-urlencode whenever a value comes from user input or contains anything beyond letters, numbers, and basic punctuation. It saves you from chasing subtle, hard-to-spot encoding bugs later.

Adding request headers

Headers carry metadata about your request: which content type you accept, what user agent you claim to be, an authorization token, and more. The -H flag sets one header per use, and you can repeat it as many times as you need:

bash
curl \
  -H "Accept: application/json" \
  -H "User-Agent: my-script/1.0" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://httpbin.org/headers

The /headers endpoint echoes back every header it received, so you can confirm cURL sent exactly what you intended. Setting a realistic User-Agent and an explicit Accept header is also the first small step toward making automated requests look less like a default cURL script, which matters once you start fetching real sites. For a deeper treatment of header handling, see how to send HTTP headers with cURL.

Saving the body to a file

By default cURL streams the response to standard output. To write it to a file instead, use -o with a filename of your choosing, or -O to reuse the remote filename from the URL:

bash
# write to a name you pick
curl -o page.html https://httpbin.org/html

# keep the remote filename
curl -O https://httpbin.org/image/png

Use -O for downloads where the server already names the file sensibly, and -o when you want control over where the bytes land. Add -s (silent) to suppress the progress meter when you are scripting, and -f (fail) to make cURL return a non-zero exit code on HTTP errors instead of cheerfully saving an error page.

Seeing the headers and status, not just the body

When you are debugging, the body is often the least interesting part. cURL gives you several ways to inspect the response metadata. -i prepends the response headers to the body, -I sends a HEAD request and shows only the headers, and -D - dumps the headers to a destination (here - means standard output) while the body goes wherever -o sends it:

bash
# body with headers on top
curl -i https://httpbin.org/get

# headers only (HEAD request)
curl -I https://httpbin.org/get

# headers to stdout, body to a file
curl -D - -o body.html https://httpbin.org/html

Reach for -I when you only care about metadata such as content type, content length, caching headers, or the status line, without downloading the whole payload. That is the fast way to check whether a resource exists or what type it is before you commit to pulling it.

Following redirects

Servers frequently answer a GET with a 301 or 302 redirect pointing somewhere else. cURL does not follow redirects on its own, so a bare request against a redirecting URL returns the short redirect response, not the final page. Add -L (location) to make cURL chase the redirect to its destination:

bash
curl -L "https://httpbin.org/redirect-to?url=https://httpbin.org/get"

Without -L you get the redirect itself; with it you get the content at the final URL. If you want to cap how many hops cURL will follow, add --max-redirs with a number so a redirect loop cannot run forever.

Printing the status code and timing

For health checks and scripts you often want a single fact, like the HTTP status code or how long the request took, rather than the whole response. The -w (write-out) flag prints chosen variables after the transfer. Pair it with -o /dev/null to discard the body and -s to stay quiet:

bash
# just the status code
curl -s -o /dev/null -w "%{http_code}\n" https://httpbin.org/get

# status plus total time in seconds
curl -s -o /dev/null \
  -w "status=%{http_code} time=%{time_total}s\n" \
  https://httpbin.org/get

The first command prints something like 200 and nothing else, which is perfect for a shell conditional or a monitoring cron job. Other useful -w variables include %{size_download}, %{num_redirects}, and %{url_effective}, the last of which is handy for seeing where a chain of redirects actually landed.

Handling JSON API responses

Many GET endpoints return JSON. To signal that you want JSON, set the Accept header, and to read the result comfortably, pipe cURL's output into jq, the command-line JSON processor. jq pretty-prints the payload and lets you select fields with a small query language:

bash
# pretty-print the whole JSON response
curl -s -H "Accept: application/json" https://httpbin.org/get | jq .

# pull a single field out of the response
curl -s https://httpbin.org/get | jq '.headers["User-Agent"]'

The -s flag is important when piping: it strips the progress meter so only clean JSON reaches jq. From there you can extract exactly the values you need, which turns cURL plus jq into a quick, scriptable API client without writing a line of program code.

Verbose mode for debugging

When a request behaves in a way you cannot explain, -v (verbose) shows the full conversation: the DNS and TLS setup, the exact request line and headers cURL sent (prefixed with >), and the response status and headers it received (prefixed with <):

bash
curl -v https://httpbin.org/get

Verbose output is the fastest way to answer questions like "did my header actually get sent?" or "where is this redirect going?" If you need to capture the raw bytes for a bug report, --trace-ascii trace.txt writes an even more detailed log to a file.

A handy mental model

Think of GET requests in two layers. The first is what to send: the URL, query parameters, and headers. The second is what to see: the body, the headers, the status code, and the timing. Almost every cURL flag above belongs to one of those two layers, which makes the long option list much easier to keep straight.

Where a raw GET stops being enough

A GET request is the foundation of web scraping: pulling a page is, at bottom, sending a GET and reading the HTML that comes back. For static pages and friendly APIs, cURL alone gets the job done. The walls go up once you point it at modern commercial sites. Two limits show up fast.

First, cURL fetches the raw HTML and nothing more. It does not run JavaScript, so any page that builds its content in the browser hands cURL an near-empty shell. Second, sites that do not want bots watch for scraper-shaped traffic: a default User-Agent, a datacenter IP, and a burst of requests from one address get challenged or blocked, often with a CAPTCHA, long before you have collected anything useful. You can paper over some of this by rotating IPs and rendering pages yourself. See cURL for web scraping and how to use cURL with a proxy for those techniques, but maintaining a headless browser fleet and a healthy proxy pool quickly becomes most of the work.

Crawlbase Crawling API

When a plain GET hits JavaScript rendering or anti-bot blocks, the Crawling API folds rendering, residential IP rotation, and CAPTCHA handling into one request. You send a GET with your token and the target URL, it renders the page behind a trusted IP, and it returns finished HTML for you to parse. The same cURL skills carry straight over. Start on the free tier first.

Because the API takes a normal GET, your existing cURL knowledge maps onto it directly. You call its endpoint with two query parameters, your token and the URL you want, and the rendered HTML comes back in the response body:

bash
curl -G https://api.crawlbase.com/ \
  --data-urlencode "token=YOUR_CRAWLBASE_TOKEN" \
  --data-urlencode "url=https://www.example.com"

This is the same -G plus --data-urlencode pattern from earlier, which is why it matters to get the encoding right: the target URL is itself a value inside the query string, so it must be encoded cleanly. Swap in the JavaScript token instead of the normal one when the target renders client-side, and the API runs the page in a real browser before returning the HTML.

Recap

Key takeaways

  • GET is the default. curl URL sends a GET, so you never specify the method for a plain fetch; -X GET is optional.
  • Parameters two ways. Quote them in the URL, or build them with -G and -d, and use --data-urlencode whenever a value contains spaces or special characters.
  • Control what you see. -H sets headers, -o and -O save the body, -i, -I, and -D reveal headers, and -w prints the status code and timing.
  • Follow and inspect. Add -L to follow redirects, pipe JSON into jq, and use -v when you need to debug the full exchange.
  • Know the ceiling. A raw GET cannot render JavaScript and gets blocked at scale; the Crawling API takes the same GET shape and returns rendered, unblocked HTML.

Frequently Asked Questions (FAQs)

Do I need to specify the method to send a GET request with cURL?

No. cURL uses GET by default, so curl https://example.com already sends a GET request. You can write it out as curl -X GET https://example.com if you want to be explicit, but it changes nothing for a normal fetch. You only need -X when you want a different method, such as -X POST or -X DELETE.

How do I pass query parameters in a cURL GET request?

Two ways. Put them directly in the URL inside quotes, like curl 'https://example.com/data?a=1&b=2', or let cURL build the query string with curl -G -d "a=1" -d "b=2" https://example.com/data. Both send the same request. If any value contains spaces, ampersands, or other special characters, switch the -d flags to --data-urlencode so cURL encodes them correctly.

How do I see only the HTTP status code?

Use the write-out flag while discarding the body: curl -s -o /dev/null -w "%{http_code}\n" https://example.com. The -s hides the progress meter, -o /dev/null throws away the response body, and -w "%{http_code}\n" prints just the numeric status. This is the standard one-liner for health checks and shell conditionals.

Why does cURL not follow a redirect?

cURL does not follow redirects unless you ask it to. When a server returns a 301 or 302, a plain request shows you that short redirect response rather than the destination. Add -L (or --location) and cURL will follow the redirect to the final URL. Use --max-redirs to limit how many hops it will chase.

How do I fetch and read JSON from an API with cURL?

Set the Accept header and pipe the output into jq. For example, curl -s -H "Accept: application/json" https://example.com/api | jq . pretty-prints the payload, and jq '.field' extracts a specific value. The -s flag keeps the progress meter out of the pipe so only clean JSON reaches jq.

Can I scrape a JavaScript-heavy site with a cURL GET?

Not directly. cURL fetches the raw HTML but does not run JavaScript, so pages that render content in the browser come back nearly empty, and anti-bot systems block default cURL traffic quickly. To handle those targets, send the GET through a rendering service like the Crawling API, which runs the page in a real browser behind a rotating residential IP and returns the finished HTML for you to parse.

Start Building

Crawl any site at scale, without fighting infrastructure.

Crawlbase handles proxies, fingerprints, and CAPTCHAs so your team ships data pipelines instead of maintaining crawl plumbing. 1,000 requests free, no card required.

Self-serve · No sales call required · Enterprise crawl volumes available