Geonode logo
Geonode Team

Geonode Team

Updated: September 1, 2026

Published: 2026-09-02

Send a GET Request with curl

A GET request in curl is the default, which means the shortest correct command is just curl followed by a URL. Everything else is refinement. The refinements are where the mistakes live: -X GET that quietly breaks redirects, query strings mangled by the shell, and a proxy setting that leaks your DNS lookups without telling you. This guide covers the working commands, what each flag actually does according to curl's own documentation, and the handful of traps that cost people afternoons.

A GET request is what curl does when you do not tell it to do anything else. That makes the shortest complete answer to this question one word long:

curl https://example.com

That is a GET request. It is correct, it is idiomatic, and if that is all you needed you can stop reading.

We are Geonode, and we sell proxies, so the honest note belongs here at the top: almost nothing in this article needs a proxy. curl talks to the internet perfectly well on its own, and the overwhelming majority of GET requests — hitting an API, downloading a file, checking whether a service is up, debugging a header — involve no proxy of any kind. There is one section below on routing curl through a proxy, because people do genuinely need it for geographic testing and for scraping at volume, and one further section on the cases where reaching for a proxy is the wrong move. We would rather you skipped both than bought something you did not need.

What this guide is actually for is the layer above the one-liner: assembling query strings without the shell eating them, seeing what really came back rather than guessing, and avoiding the two or three traps that are genuinely surprising. Every behaviour described below is taken from curl's own documentation and the curl book, both maintained by the project itself, rather than from anyone's recollection.

The most useful of those traps is in the very next section, and it involves a flag that thousands of tutorials tell you to use.

The Simplest GET Request

Start here and add only what you need.

curl https://api.example.com/users

curl sends a GET request and writes the response body to standard output. No method flag, no headers, nothing else required.

Save It to a File

Two ways, and the difference matters.

# Write to a filename you choose
curl -o users.json https://api.example.com/users

# Write using the filename from the URL
curl -O https://example.com/files/report.pdf

Lowercase -o takes a filename. Uppercase -O uses the last path segment of the URL, which is convenient for downloads and useless for API endpoints that end in a path with no filename.

Quiet It Down

By default curl writes a progress meter to standard error whenever output is not a terminal, which clutters logs and scripts.

curl -s https://api.example.com/users

-s silences the progress meter — and, unhelpfully, error messages too. In scripts you almost always want:

curl -sS https://api.example.com/users

-sS means "silent, but still show errors". This combination should be your default in anything automated. A silent curl that fails silently is a bug waiting to be diagnosed at an inconvenient time.

Fail Properly on HTTP Errors

This one catches nearly everyone.

curl -sS https://api.example.com/missing
# prints the error page body, exits with status 0

curl treats a 404 as a successful transfer, because the transfer succeeded — the server responded. The exit code reflects whether curl worked, not whether the server was happy.

curl -sSf https://api.example.com/missing
# prints nothing, exits non-zero

-f makes curl fail silently on HTTP errors of 400 and above and return a non-zero exit code. In any script where a failed request should stop the pipeline, -sSf is the combination you want, and its absence is one of the most common reasons a broken cron job appears to be working.

Stop Writing -X GET

A large fraction of the curl examples on the internet include -X GET. It is unnecessary, and in one specific and common situation it is actively harmful.

Why It Is Unnecessary

The curl book puts it plainly: "when asking curl to perform HTTP transfers, it picks the correct method based on the option so you should only rarely have to explicitly ask for it with -X."

curl already knows. A plain command is GET. Add -d and it becomes POST. Add -I and it becomes HEAD. Add -T and it becomes PUT. The method follows from what you asked for, and stating it again adds nothing.

Why It Can Break Things

Here is the part worth remembering. From the same documentation: "when curl follows redirects like asked to with -L, the request method set with -X is sent even on the subsequent redirects."

That is the trap. Normally, curl adjusts the method appropriately as it follows a redirect chain. Force the method with -X and it stops adjusting — your forced method is sent to every URL in the chain, whatever the redirect was actually asking for.

With -X GET on a GET request this is usually harmless, because GET was the answer anyway. It stops being harmless the moment someone copies the pattern into a command that posts data and follows redirects, at which point the request quietly becomes wrong in a way that is genuinely unpleasant to debug — the command looks fine and the server sees something else.

The related documented case is -X HEAD, which hangs: HEAD responses carry no body, but curl has been told the method rather than the intent, so it waits for data that is never coming.

The Rule

Use -X only when you need a method curl has no option for — DELETE, PATCH, or something custom. For GET, omit it. The shorter command is also the more correct one, which is a rare and pleasing combination.

Building Query Strings Properly

GET requests carry their parameters in the URL, and this is where the shell starts causing trouble.

Quote Your URLs

# Broken: & backgrounds the command, ? may glob
curl https://api.example.com/search?q=test&page=2

# Correct
curl "https://api.example.com/search?q=test&page=2"

In a shell, & separates commands and ? is a wildcard character. An unquoted URL containing them will be split, mangled, or silently truncated. Always quote URLs. This costs nothing and prevents a category of confusing failure.

Let curl Build the Query String

Once you have more than two parameters, or any parameter containing a space, hand-assembling the URL becomes tedious and error-prone. curl has a better way.

The -G flag, per the documentation, "makes all data specified with --data, --data-binary or --data-urlencode to be used in an HTTP GET request instead of the POST request".

curl -G https://api.example.com/search \
  --data-urlencode "q=proxy servers" \
  --data-urlencode "country=United Kingdom" \
  --data-urlencode "page=2"

curl assembles this into ?q=proxy%20servers&country=United%20Kingdom&page=2 and sends a GET. The spaces, the ampersands, the accented characters — all encoded correctly, without you thinking about it.

This is the single most useful thing in this article for anyone working with APIs regularly. Manual percent-encoding is a job for a computer, and you have one right there.

The --data-urlencode Forms

The documentation lists several syntaxes, and they behave differently:

FormWhat it does
contentEncodes the whole string, including any =
=contentEncodes content, no parameter name emitted
name=contentEncodes only the content, leaves name alone
@filenameEncodes the file's contents
name@filenameEncodes the file's contents under name

The one to internalise is name=content, which is what you want almost every time: the parameter name passes through untouched and the value gets encoded. Getting this backwards — encoding the name as well — produces requests that fail in ways the server error message will not explain.

Seeing What Actually Happened

The response body alone often does not tell you what you need to know.

Headers Alongside the Body

curl -i https://api.example.com/users

-i includes the response headers before the body. Useful when you need the status line, content type, rate limit headers, or a Set-Cookie you were not expecting.

curl -I https://example.com

Uppercase -I sends a HEAD request and shows headers only. Handy for checking whether a resource exists, what size it claims to be, and where a URL redirects to — without downloading anything.

Full Verbose Output

curl -v https://api.example.com/users

-v shows the request curl sent as well as the response, with > marking outgoing lines and < incoming. This is the first thing to reach for when a request behaves differently from what you expected, because it shows what was actually sent rather than what you meant to send — and those differ more often than anyone admits.

For even more detail, --trace-ascii - dumps the full exchange including bodies.

Just the Status Code

For scripts and health checks:

curl -s -o /dev/null -w "%{http_code}\n" https://example.com

This discards the body, silences the progress meter, and prints only the status code. -w (write-out) supports a range of variables, and combining a few gives you a compact diagnostic line:

curl -s -o /dev/null \
  -w "status=%{http_code} time=%{time_total}s size=%{size_download}\n" \
  https://example.com

That single command is a serviceable uptime and latency check, and it composes into monitoring scripts without any additional tooling.

Timing Breakdown

When something is slow and you need to know which part:

curl -s -o /dev/null \
  -w "dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n" \
  https://example.com

The gaps between these numbers tell you where the time went: slow DNS, slow TCP handshake, slow TLS negotiation, or a server that took a long time to start responding. This is considerably more informative than a total figure, and it is the fastest way to work out whether a performance problem is yours or theirs.

Headers, Cookies and Authentication

Custom Headers

curl -H "Accept: application/json" \
     -H "X-API-Key: your-key-here" \
     https://api.example.com/users

Repeat -H for each header. Two things to know:

To remove a header curl would otherwise send, give it no value: -H "User-Agent:". To send a header with a genuinely empty value, use a semicolon: -H "X-Empty;".

And an important warning from the documentation: headers set with -H "are set in all HTTP requests — even after redirects are followed", so "sensitive headers should be used with caution". If a request redirects to a host you did not anticipate, your API key goes with it.

Authentication

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

# Bearer token
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com/private

# Prompt for the password instead of putting it in shell history
curl -u username https://api.example.com/private

That last form is worth the extra keystroke. Passwords on the command line end up in your shell history and are visible in the process list to anyone else on the machine. Omitting the password makes curl prompt for it.

Cookies

# Send cookies inline
curl -b "session=abc123; lang=en" https://example.com

# Save cookies the server sets
curl -c cookies.txt https://example.com/login

# Send them back on the next request
curl -b cookies.txt https://example.com/dashboard

-c writes a cookie jar, -b reads one. Using both together across a sequence of requests is how you maintain a session, which is what most "why does this work in my browser but not in curl" problems turn out to be about.

Redirects, Compression and Timeouts

Three flags that belong in nearly every real-world command.

Follow Redirects

curl -L https://example.com/old-page

curl does not follow redirects by default. It reports the 301 and stops. -L tells it to follow, and --max-redirs caps how far.

One security detail worth knowing, from the documentation: Authorization and Cookie headers "are explicitly not passed on in HTTP requests when following redirects to other origins, unless --location-trusted is used". That is sensible default behaviour — your credentials do not follow you to a different host. Note the asymmetry with -H headers described above, which do follow. If you set an API key with -H rather than -u, curl does not know it is a credential and will happily send it onward.

Compression

curl --compressed https://example.com

This requests a compressed response and, per the documentation, "automatically decompress[es] the content". On text-heavy responses it substantially reduces transfer size, which matters whenever bandwidth is metered.

The documentation attaches a warning worth respecting: "when decompressing data, even tiny transfers might be expanded and generate a huge amount of bytes." A small compressed response can become an enormous decompressed one. If you are pointing curl at untrusted URLs in an automated system, cap the output with --max-filesize rather than assuming a small download stays small.

Timeouts

curl --connect-timeout 5 --max-time 30 https://example.com

The two are different and both are useful. --connect-timeout "only limits the connection phase" — if curl connects within that window it continues regardless of how long the rest takes. --max-time limits the whole operation and "accepts decimal values".

Without these, a curl in a script can hang effectively forever against an unresponsive server. Every curl in automation should have a --max-time. It is the cheapest reliability improvement available and it costs eleven characters.

Retries

curl --retry 3 --retry-delay 2 --retry-max-time 60 https://api.example.com/data

Retries on transient failures with a delay between attempts. Combine with -sSf so failures actually surface once the retries are exhausted.

Sending a GET Through a Proxy

This is our product's territory, so treat the enthusiasm accordingly — but the mechanics are worth knowing and one detail here is a genuine trap.

The Basic Syntax

curl -x http://proxy.example.com:8080 https://api.example.com/data

# With authentication
curl -x http://username:password@proxy.example.com:8080 https://api.example.com/data

# Or separately
curl -x http://proxy.example.com:8080 --proxy-user username:password https://api.example.com/data

A useful default to know: per the curl book, "the default proxy type is HTTP so if you specify a proxy hostname (or IP address) without a scheme part... curl goes with assuming it is an HTTP proxy." If you meant SOCKS and omitted the scheme, curl has quietly done something else.

SOCKS, and the DNS Leak Nobody Mentions

curl accepts several SOCKS forms:

curl -x socks5://proxy.example.com:1080 https://example.com
curl -x socks5h://proxy.example.com:1080 https://example.com

They look interchangeable. They are not, and this is the detail worth taking away from the section.

With socks5, per the documentation, "curl resolves the name" — locally, on your machine, before connecting to the proxy. With socks5h, curl "sends the hostname to the proxy so there is no name resolving done by curl locally".

The consequence: socks5 leaks every hostname you visit to your local DNS resolver, which is typically your ISP or your network operator. The traffic goes through the proxy; the lookup telling everyone where it is going does not. If you chose a proxy for geographic or privacy reasons, socks5 partially defeats it. It also breaks on any hostname that only resolves correctly from the proxy's network.

Use socks5h unless you have a specific reason not to. The h is for hostname, it is one character, and it is the difference between routing your traffic and routing your traffic while announcing it.

Verifying the Proxy Is Actually Being Used

curl -x http://proxy.example.com:8080 https://api.ipify.org

If the address returned is the proxy's rather than yours, it is working. Add -v to see the connection curl actually made — which is how you catch a typo in the proxy URL that curl silently ignored.

Environment Variables

export https_proxy="http://proxy.example.com:8080"
curl https://example.com   # uses the proxy without -x

Convenient, and a common source of confusion when someone else set them. If a proxy appears to be in use and no -x is present, check the environment before checking anything else.

When You Do Not Need Any of This

Against our own interest: several situations where reaching for a proxy — or for curl at all — is the wrong move.

Ordinary API calls. If you are hitting an API you have credentials for, from a server that is allowed to reach it, add nothing. A proxy adds latency, a failure point and a bill for no benefit. The great majority of curl usage falls here.

A handful of requests to any target. Rate limiting and bot detection respond to volume and pattern. Ten requests spread over an afternoon look like a person. Adding proxies to a small job solves a problem you do not have.

Downloading files. curl -O from a public URL needs nothing else. Proxies make it slower and more expensive.

Testing your own infrastructure. You control both ends. Route directly and see real numbers rather than numbers plus a proxy hop.

Anything where a browser is the actual requirement. If the target renders its content with JavaScript, curl gets you the empty shell and no proxy fixes that. You need a headless browser, and swapping proxies while the page stays blank is a well-trodden waste of an afternoon.

When the block is not about your address. Missing headers, absent cookies, a stale token, the wrong content type. Diagnose with -v before assuming the address was the problem — buying proxies to fix a malformed request is an expensive way to keep the bug.

Proxies with curl genuinely earn their place in three cases: checking how a site or an advert renders from a specific country, collecting data at volume where per-address rate limits are the binding constraint, and reaching services that are geographically restricted for legitimate reasons. Outside those, the plain command is the better command.

People Also Ask

How do I send a GET request with curl?

Just give curl a URL: curl https://example.com. GET is the default method, so no flag is required. Quote the URL if it contains a query string, because & and ? mean something to your shell.

Do I need -X GET with curl?

No. curl picks the method from the options you use, and the documentation advises against specifying it unnecessarily. It also has a real downside: when following redirects with -L, a method forced with -X is sent to every URL in the chain rather than being adjusted per redirect.

How do I add query parameters in curl?

Either put them in a quoted URL, or let curl build them: curl -G https://api.example.com/search --data-urlencode "q=some value". The second approach handles percent-encoding for you and is far less error-prone once you have more than a couple of parameters.

How do I see the response headers in curl?

-i shows headers plus body, -I sends a HEAD request and shows headers only, and -v shows the full exchange including the request curl actually sent. For debugging, -v first.

Why does curl return nothing?

Usually a redirect that was not followed — curl does not follow redirects unless you pass -L. It can also be a genuinely empty body, or output sent to a file with -o. Run the command with -i to see the status code and find out which.

How do I make curl fail on HTTP errors?

Add -f. By default curl treats a 404 or 500 as a successful transfer and exits zero, because the transfer did succeed. curl -sSf — silent, show errors, fail on HTTP errors — is the sensible default for scripts.

How do I use a proxy with curl?

curl -x http://proxy.example.com:8080 https://example.com, with credentials as user:pass@host or via --proxy-user. If you omit the scheme, curl assumes HTTP. For SOCKS, use socks5h:// rather than socks5://.

What is the difference between socks5 and socks5h in curl?

Who resolves the hostname. With socks5, curl resolves it locally, which means your DNS provider sees every host you visit even though the traffic goes through the proxy. With socks5h, the hostname is sent to the proxy and resolved there. Prefer socks5h.

Wrapping Up

The correct GET request in curl is curl followed by a URL. Everything beyond that is refinement, and the refinements worth internalising are few.

Drop -X GET. It is redundant, the documentation recommends against it, and it changes redirect behaviour in a way that becomes a genuine bug the moment the pattern gets copied into a command that sends data.

Let curl build query strings. -G with --data-urlencode handles the encoding correctly, every time, and removes an entire class of failure that produces unhelpful server errors.

Use -sSf and --max-time in anything automated. Silent but not about errors, non-zero exit on HTTP failures, and a hard ceiling on how long a request may hang. Three flags that turn curl from a convenient tool into a dependable one.

Reach for -v before you reach for theories. Most surprising behaviour resolves the moment you see what was actually sent rather than what you intended to send.

On our own product: the section above on proxies is there because the mechanics are genuinely fiddly, not because you should be using one. Ordinary API calls, file downloads and small jobs need nothing more than the plain command, and adding a proxy to them buys latency and a bill. Where they do earn their place — geographic checking, volume work bounded by per-address rate limits — the one detail that matters more than the provider is using socks5h rather than socks5, so that your DNS lookups travel the same route as your traffic.

One character, and it is the difference between routing your requests and announcing them.

How to Send a GET Request with curl (+Examples) | Geonode