Skip to content

curl Command Deep Dive


curl is often introduced as “a command for downloading something from a URL.” That description is technically true but misses why it is so useful on Linux. curl is a compact way to interact with network services directly from the shell, especially HTTP services. Once you understand how to control the request and inspect the response, it becomes useful for testing services, debugging HTTP behavior, downloading files, sending data, and automating repetitive network tasks.

What Is curl Actually Doing?

At its simplest:

curl https://example.com

asks curl to retrieve the resource at that URL.

The important part is what happens underneath:

curl
 │
 ├── DNS resolution
 │
 ├── TCP connection
 │
 ├── TLS negotiation
 │
 └── HTTP request
        │
        ▼
     Web server
        │
        ▼
   HTTP response

So curl is an application that exercises several parts of the Linux networking stack you have already studied.

If the URL uses HTTPS, there is another layer between TCP and HTTP:

curl
  ↓
TCP
  ↓
TLS
  ↓
HTTP

This makes curl particularly valuable for troubleshooting because you can inspect or control each part of the request instead of treating a browser as one opaque operation.

Start With A Simple Request

Run:

curl https://example.com

The response is written to your terminal.

If the server returns HTML, you’ll see HTML.

This is different from opening the page in a browser. A browser normally takes the response and renders it into a visual document. curl simply gives you the HTTP response body.

That makes it ideal when you care about what the server actually returned.

See The HTTP Headers

A useful first step beyond the basic request is:

curl -i https://example.com

The -i option includes the response headers together with the response body.

You may see something conceptually like:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1256
...

<!doctype html>
<html>
...

Now you can see that an HTTP response isn’t just the body.

It contains metadata such as:

Status
Headers
Body

This distinction becomes extremely important when debugging web services.

Request Headers And Response Headers Are Different

There are two directions of headers.

The server sends response headers:

Server → curl

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1256

The client can send request headers:

curl → Server

GET / HTTP/1.1
Host: example.com
User-Agent: ...
Accept: ...

You can inspect the request that curl sends with:

curl -v https://example.com

This is one of the most useful curl options to learn.

-v: See The Conversation

Run:

curl -v https://example.com

The verbose output shows connection and protocol details.

You’ll see information corresponding to stages such as:

DNS / address selection
    ↓
TCP connection
    ↓
TLS negotiation
    ↓
HTTP request
    ↓
HTTP response

The output contains a lot more information than the normal command because curl is exposing what it is doing.

For HTTP, lines beginning with:

>

represent data sent by curl.

Lines beginning with:

<

represent data received from the server.

For example:

> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/...
> Accept: */*

< HTTP/1.1 200 OK
< Content-Type: text/html

This is an excellent debugging view because you can distinguish:

What did I send?

from:

What did the server send back?

-I: Request Headers Without The Body

Sometimes you don’t care about the response body.

Use:

curl -I https://example.com

This requests headers only using an HTTP HEAD request.

You might get:

HTTP/1.1 200 OK
content-type: text/html
content-length: 1256

This is useful for quickly checking things such as:

  • whether a resource exists
  • the returned status code
  • the content type
  • the reported size
  • redirect behavior
  • caching-related headers

There is an important difference between:

curl -i

and:

curl -I

-i includes the headers with the normal response body.

-I asks for headers without downloading the normal body.

HTTP Status Codes

When working with HTTP, the status code is one of the first things you should inspect.

For example:

200 OK

means the request succeeded.

Common categories are:

2xx → success
3xx → redirection
4xx → client-side/request problem
5xx → server-side problem

Examples:

200 OK
301 Moved Permanently
302 Found
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

curl doesn’t hide these from you. You can inspect them with:

curl -I https://example.com

or:

curl -i https://example.com

For automated checks, you can also ask curl to print only the status code:

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

Here:

-s

suppresses normal progress output,

-o /dev/null

discards the response body,

and:

-w

lets you print selected information about the transfer.

This pattern is useful in scripts because you can turn an HTTP request into a simple value such as:

200

Download A File

One of the most common uses of curl is downloading a file.

Suppose:

https://example.com/archive.tar.gz

points to a file.

You can write the response to a specific filename:

curl -o archive.tar.gz https://example.com/archive.tar.gz

The general pattern is:

curl -o <local-file> <URL>

Without -o, curl writes the response body to standard output, which is why:

curl https://example.com/archive.tar.gz

would attempt to dump the file into your terminal.

For a URL whose final path already contains the desired filename, -O is convenient:

curl -O https://example.com/archive.tar.gz

Now curl uses the remote filename:

archive.tar.gz

So the distinction is:

-o filename
    You choose the local filename.

-O
    Use the filename from the URL.

Follow Redirects

HTTP resources frequently redirect.

For example:

http://example.com
       ↓
https://example.com

A server may respond with:

301 Moved Permanently
Location: https://example.com/

By default, curl does not necessarily follow every redirect automatically.

Use:

curl -L https://example.com

The -L option tells curl to follow redirects.

This becomes particularly important when downloading files or interacting with modern websites.

For example:

curl -L -O https://example.com/file.tar.gz

means:

follow redirects
+
save using the remote filename

Why Redirects Matter During Debugging

Suppose:

curl http://example.com

returns:

301 Moved Permanently
Location: https://example.com/

You might initially think the site is broken because you didn’t receive the page.

But the server actually answered correctly.

The conversation is:

curl
 │
 │ GET http://example.com
 ▼
Server
 │
 │ 301
 │ Location: https://example.com/
 ▼
curl

With:

curl -L http://example.com

curl follows that instruction and performs the next request.

This is a good example of why inspecting the HTTP response is often more useful than simply saying “the command didn’t work.”

Send A Specific HTTP Method

By default, curl uses GET for a normal request.

You can explicitly select a method:

curl -X GET https://example.com

For example, you may encounter APIs that use:

GET
POST
PUT
PATCH
DELETE

You can specify one with:

curl -X POST https://example.com/api/items

However, don’t automatically use -X just because you know the HTTP method.

curl has options that naturally imply certain methods.

For example, sending form data with:

curl -d 'name=alice' https://example.com/api/items

causes curl to use POST.

The important lesson is:

Use the option that represents what you’re trying to do; don’t add -X unless you actually need to override the method.

Send Form Data

A common HTTP pattern is sending form data.

For example:

curl -d 'username=alice&password=secret' https://example.com/login

The server receives a POST request containing the supplied data.

You can make the request more explicit:

curl   -H 'Content-Type: application/x-www-form-urlencoded'   -d 'username=alice&password=secret'   https://example.com/login

The -H option adds a request header.

The important relationship is:

-H
    controls request headers

-d
    supplies request data

This is enough to reproduce many simple HTTP requests from a browser or another client.

Warning

Avoid putting real passwords or API tokens directly into shell history while experimenting. Use mock credentials in examples and be aware that shell history can retain commands.

Send JSON

Modern APIs commonly accept JSON.

For example:

curl   -H 'Content-Type: application/json'   -d '{"name":"alice","role":"admin"}'   https://example.com/api/users

Now the request contains:

Content-Type: application/json

and the body:

{"name":"alice","role":"admin"}

This is one of the most common curl patterns you’ll encounter when working with HTTP APIs.

You can combine it with verbose mode:

curl -v   -H 'Content-Type: application/json'   -d '{"name":"alice","role":"admin"}'   https://example.com/api/users

Now you can see the request and response around the JSON operation.

Add A Custom Request Header

The basic syntax is:

curl -H 'Header-Name: value' https://example.com

For example:

curl -H 'X-Request-ID: test-123' https://example.com

This is useful for reproducing requests where an application expects a particular header.

You can also provide common HTTP headers:

curl   -H 'Accept: application/json'   https://example.com/api/users

Now you’re asking the server for a JSON representation if it supports one.

Headers are one of the reasons curl is so useful for debugging. You can reproduce the important parts of an HTTP request without needing the original application.

Authentication Headers

A common API pattern is bearer-token authentication:

curl   -H 'Authorization: Bearer YOUR_TOKEN'   https://example.com/api/users

The important part is not the particular token.

It is the HTTP pattern:

Authorization: Bearer <token>

Similarly, basic authentication can be expressed with:

curl -u alice:password https://example.com/private

This asks curl to construct the appropriate HTTP authentication header.

Again, use fake credentials when practicing.

Cookies

curl can also work with HTTP cookies.

To send a cookie:

curl   -H 'Cookie: session=abc123'   https://example.com/account

This is useful when reproducing a request from an authenticated web session.

You can also let curl store cookies:

curl -c cookies.txt https://example.com/login

and use them later:

curl -b cookies.txt https://example.com/account

The pattern is:

-c
    write cookies to a file

-b
    read cookies from a file

This is useful when a multi-step interaction depends on session cookies.

Save And Reuse A Request

Because curl commands are plain shell commands, they are easy to reproduce.

For example:

curl   -H 'Accept: application/json'   -H 'X-Request-ID: test-123'   https://example.com/api/users

You can run the same command repeatedly while changing one variable at a time.

That makes curl particularly useful for debugging.

Instead of:

Browser
   ↓
many things happening automatically
   ↓
something fails

you can reduce the interaction to:

curl
  ↓
specific request
  ↓
specific response

This reduction is one of its biggest strengths.

Inspect Only The Headers First

When debugging an HTTP endpoint, a good first move is often:

curl -I https://example.com

Then, if you need more detail:

curl -v https://example.com

Then, if you need to reproduce the exact request:

curl   -H 'Accept: application/json'   https://example.com/api

This gives you a progression:

Status / response headers
        ↓
Connection + request + response details
        ↓
Custom request reproduction

You don’t always need the most verbose command immediately.

Debug A Connection With -v

Suppose:

curl https://example.com

fails.

Try:

curl -v https://example.com

Now you can ask several separate questions.

Did DNS resolve the hostname?

* Host example.com:443 was resolved.

Did TCP connect?

* Connected to example.com (...)

Did TLS negotiation begin and succeed?

* SSL connection using ...

Was an HTTP request sent?

> GET / HTTP/1.1

Did the server respond?

< HTTP/1.1 200 OK

This is much more informative than simply seeing an error message.

-s, -S, And -sS

When using curl in scripts, you often don’t want the normal progress information.

Use:

curl -s https://example.com

-s means silent.

But completely silent mode can also hide useful error information.

A common combination is:

curl -sS https://example.com

This means roughly:

-s
    hide normal progress output

-S
    still show errors

This is a very common scripting pattern.

Fail On HTTP Errors

There is an important distinction between:

curl successfully connected to the server

and:

the HTTP request succeeded

For example:

curl https://example.com/not-found

may receive:

404 Not Found

From the HTTP perspective, the server successfully returned a response.

For scripting, you may instead want curl to treat HTTP 4xx/5xx responses as failures.

Use:

curl --fail https://example.com/not-found

The short option is:

curl -f https://example.com/not-found

This distinction matters when writing scripts:

Network request completed
        ≠
HTTP operation succeeded

A script that only checks whether curl could connect may accidentally treat an HTTP 404 or 500 as success.

Set A Timeout

A command that can wait forever is inconvenient in automation.

You can set a maximum time for the operation:

curl --max-time 10 https://example.com

Now the operation is limited to 10 seconds.

You can also set a connection timeout:

curl --connect-timeout 5 https://example.com

This focuses specifically on how long curl should wait while establishing the connection.

The distinction is useful:

--connect-timeout
    How long to wait for connection establishment.

--max-time
    Maximum time for the entire operation.

For a script, a pattern such as:

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

is much safer than allowing a network problem to leave the script waiting indefinitely.

Download Files Reliably

For a simple download:

curl -L -O https://example.com/file.tar.gz

For a script, you may want failure detection and useful errors:

curl -fL -o file.tar.gz https://example.com/file.tar.gz

This combines:

-f
    fail on HTTP errors

-L
    follow redirects

-o
    choose the output filename

This is a useful general-purpose download pattern.

Continue A Partial Download

If a large download is interrupted, HTTP servers may support resuming it.

Use:

curl -C - -O https://example.com/large-file.iso

The -C - tells curl to continue from the existing local file position.

This is useful for large files where restarting the entire download would be wasteful.

Whether resume works depends on the remote server supporting the necessary range requests.

Download Multiple Files

You can invoke curl repeatedly:

curl -O https://example.com/file1.tar.gz
curl -O https://example.com/file2.tar.gz
curl -O https://example.com/file3.tar.gz

You can also use multiple URLs in one command:

curl -O https://example.com/file1.tar.gz  \
     -O https://example.com/file2.tar.gz   \
     -O https://example.com/file3.tar.gz

This is useful when you know exactly which resources you need.

Download A Whole Website: Why This Is Different

A common question is:

“Can curl download an entire website?”

Not in the way people usually mean.

A web page is not necessarily one file.

Suppose:

index.html
   ├── style.css
   ├── app.js
   ├── image.png
   └── another-page.html

Downloading:

curl -O https://example.com/

gets one HTTP response.

It does not automatically crawl every linked page and dependency.

You could write a script that parses links and recursively requests them, but that quickly becomes a web crawler rather than a simple curl operation.

For a practical “download this site and its linked resources” task, a crawler-oriented tool such as wget is often a better fit.

That difference is important:

curl
    → excellent at making controlled HTTP requests

wget
    → designed more around downloading resources
       and recursive retrieval

We’ll examine this distinction properly in the next lesson.

curl As A Service Debugging Tool

Imagine a service is supposed to expose:

https://api.example.com/health

A useful progression is:

curl -I https://api.example.com/health

If you need connection details:

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

If the endpoint expects JSON:

curl   -H 'Accept: application/json'   https://api.example.com/health

If authentication is required:

curl   -H 'Authorization: Bearer TEST_TOKEN'   https://api.example.com/health

If you need the HTTP status in a script:

curl -sS -o /dev/null -w '%{http_code}\n'   https://api.example.com/health

The important part is that each command answers a different question.

curl And The Layers You Have Learned

At this point, the networking topics connect together:

curl https://example.com
        │
        ▼
DNS
        │
        ▼
IP address
        │
        ▼
Routing
        │
        ▼
Network interface
        │
        ▼
TCP connection to port 443
        │
        ▼
TLS
        │
        ▼
HTTP request
        │
        ▼
HTTP response

This is why curl is such a good networking tool for this course.

It doesn’t replace the lower-level commands.

Instead, it gives you a practical application whose behavior you can explain using those lower-level concepts.

When Should You Reach For curl?

A useful rule is:

Use curl when you want to make or inspect a specific request.

Examples:

TaskUseful curl pattern
Fetch a URLcurl URL
See response headerscurl -i URL
Request headers onlycurl -I URL
Inspect the connectioncurl -v URL
Follow redirectscurl -L URL
Download with chosen namecurl -o file URL
Download using remote namecurl -O URL
Send form datacurl -d '...' URL
Send JSONcurl -H 'Content-Type: application/json' -d '...' URL
Add a headercurl -H 'Name: value' URL
Show only statuscurl -sS -o /dev/null -w '%{http_code}\n' URL
Fail on HTTP errorscurl -f URL
Set a timeoutcurl --max-time 10 URL

You don’t need to memorize this table. The goal is to recognize the patterns and know what question each one answers.

What You Should Remember

curl is much more than a downloader.

It is a controlled HTTP client that lets you see and manipulate the request/response exchange.

The most useful progression is:

curl URL
    ↓
basic response

curl -i URL
    ↓
response headers + body

curl -I URL
    ↓
headers only

curl -v URL
    ↓
connection + request + response details

curl -H ... -d ... URL
    ↓
reproduce a specific HTTP request

For downloads:

-o
    choose the local filename

-O
    use the remote filename

-L
    follow redirects

-C -
    resume a partial download

And for automation:

-f
    treat HTTP errors as failures

-sS
    quiet normal output but keep errors

--connect-timeout
    limit connection establishment time

--max-time
    limit the entire operation

The larger lesson is that curl gives you a way to reduce a web interaction to something explicit and reproducible:

request
   ↓
response

That makes it useful both for ordinary downloads and for serious service debugging.

What’s Next

curl is excellent when you want precise control over an HTTP request. But downloading resources is only one part of the problem.

Next we’ll look at wget and use it to answer the practical question:

When should you use wget instead of curl?

We’ll compare their strengths through real patterns, including downloads, recursive retrieval, and website mirroring.

Last updated on