curl Command: Options, POST Requests and File Downloads

Test environmentRHEL familyAlmaLinux 9.8root accountChecked 2026-10-28

The curl command is a tool that sends a request to a web address from the terminal and prints the response as it is. With a single command you can check a page's body, headers and status code. When we sent curl requests from an AlmaLinux 9 server to a WordPress site, the http address answered with a 301 response pointing to the https address, and a REST API request without authentication was rejected with 401.

1️⃣ Basic curl command usage and the response body

Put a single address after curl and it sends a GET request to that address and prints the response body on the screen. When we requested the robots.txt that WordPress generates, the three-line file came back exactly as it is.

curl https://example.com/robots.txt
User-agent: *
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php

It is easy to expect the same page you see after typing the address into a browser, but what curl prints is the raw HTML the server sent. curl does not run scripts or request images and CSS separately. Page HTML is long, so for a web page address we pipe the output to head and check only the first few lines.

curl -s https://example.com/ | head -n 2
<!DOCTYPE html>
<html lang="ko-KR">

Adding -s hides the progress meter that would otherwise mix into the screen when you pipe the output. The HTML received from the home page address alone was 96,679 bytes. Image and script files are not included in that size.

2️⃣ Checking headers with curl -I and following redirects with -L

The body alone does not tell you whether the request succeeded, so check the status code and headers separately with -I. -I sends a HEAD request and prints only the response headers, without the body. It is like checking the shipping label without opening the parcel.

curl -I https://example.com/
HTTP/2 200
server: nginx/1.28.0
date: Wed, 28 Oct 2026 05:29:23 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
last-modified: Tue, 27 Oct 2026 21:12:18 GMT
cache-control: max-age=0
expires: Wed, 28 Oct 2026 05:29:23 GMT
accept-ranges: none
alt-svc: h3=":443"; ma=86400

The [HTTP/2 200] on the first line is the status code. Below it come the server type, the content type and cache-related headers. If you send the same request to the http address, you get 301 along with the https address to move to in the [Location] header.

curl -I http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.28.0
Date: Wed, 28 Oct 2026 05:27:51 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://example.com/

When we requested the http address without options, curl did not request the address in [Location] again; it printed only the 7-line body of the 301 response and exited.

curl http://example.com/
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.28.0</center>
</body>
</html>

With -L, curl requests the address in [Location] again. Combine it with -I and run -IL, and the 301 response headers are followed by the 200 response headers in order.

curl -IL http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.28.0
Date: Wed, 28 Oct 2026 05:29:23 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://example.com/

HTTP/2 200
server: nginx/1.28.0
date: Wed, 28 Oct 2026 05:29:23 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
last-modified: Tue, 27 Oct 2026 21:12:18 GMT
cache-control: max-age=0
expires: Wed, 28 Oct 2026 05:29:23 GMT
accept-ranges: none
alt-svc: h3=":443"; ma=86400

The diagram below lets you compare, step by step, how many requests go out and what appears on the screen for each option.

🧭 Requests and output by curl option: a 301 from the http address
Explainer diagram
⚙️ Option
$ curl -I -L -IL http://example.com/
① GETHEAD Requesthttp://example.com/
➜
301 Moved PermanentlyLocation: https://example.com/
② GETHEAD Requesthttps://example.com/Not requested
➜
200No response
🖥️ What is printed on the screen
301 response body (7 lines of HTML)
301 response headers, 7 lines
200 response body (home page HTML, 96,679 bytes)
301 response headers, 7 lines → 200 response headers, 10 lines
Without options, curl does not request the https address in [Location] again and prints only the body of the 301 response.
-I receives only the headers with a HEAD request and, without requesting the new address, prints the 301 status and the [Location] header.
-L sends one more request to the https address in [Location] and prints the body of the final 200 response.
-IL prints the headers of both responses in order. The 301 is followed by the 200.

3️⃣ Downloading files with the curl command (-o, -O)

-O saves the file in the current folder under the last file name in the address. While saving, the screen shows a progress meter instead of the body.

curl -O https://example.com/wp-includes/images/w-logo-blue.png
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  3113  100  3113    0     0   178k      0 --:--:-- --:--:-- --:--:--  190k
ls -l w-logo-blue.png
-rw-r--r-- 1 root root 3113 Oct 28 14:29 w-logo-blue.png

After the download, [Total] and [Received] in the progress meter both read 3113, and the file size checked with ls -l was also 3,113 bytes.

To choose the file name yourself, put it after -o. Below is the result of saving the home page address as home.html.

curl -o home.html https://example.com/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 96679    0 96679    0     0  4291k      0 --:--:-- --:--:-- --:--:-- 4291k
ls -l home.html
-rw-r--r-- 1 root root 96679 Oct 28 14:29 home.html

-O uses the file name at the end of the address as it is, so it does not suit page addresses that end with a slash. Adding -O to the home page address makes curl exit with the error below because it cannot find a file name.

curl -O https://example.com/
curl: Remote file name has no length!
curl: (23) Failed writing received data to disk/application

4️⃣ curl POST requests and sending JSON data

To send data with a request that so far has only been reading, use -d, and add -H to state the data format. -d sends the data you write after it as the POST body. When nothing else is specified, the [Content-Type] is [application/x-www-form-urlencoded].

With -d the request method changes to POST, so you can leave out [-X POST]. The official documentation also says -X is usually not needed.

When sending JSON, use -H to change [Content-Type] to [application/json]. Below is the result of sending a post creation request to the WordPress REST API without authentication.

curl -s -X POST -H 'Content-Type: application/json' -d '{"title":"curl test"}' https://example.com/wp-json/wp/v2/posts
{"code":"rest_authentication_error","message":"\uc8c4\uc1a1\ud569\ub2c8\ub2e4. REST API \uc694\uccad\uc744 \ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.","data":{"status":401}}

The site we sent the request to blocks REST API requests from users who are not logged in and therefore code returned [rest_authentication_error] with status 401. Because of the same setting, a GET request for the post list at the same address also returned 401. The message value is an error sentence written in the site's language, turned into JSON Unicode escapes.

To check only the status code, discard the body with [-o /dev/null] and print only http_code with -w.

curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' -d '{"title":"curl test"}' https://example.com/wp-json/wp/v2/posts
401

5️⃣ Measuring response time with the curl command (-w)

When a response is slow it is tempting to look only at total, but to see whether the time went into the connection or into server processing, you need to print the time for each stage separately. -w prints measurements in the format you specify after the request finishes. Values fill the [%{variable name}] slots in the format.

Discard the body with [-o /dev/null] and hide the progress meter with -s.

curl -o /dev/null -s -w 'code: %{http_code}\nconnect: %{time_connect}\ntls: %{time_appconnect}\nttfb: %{time_starttransfer}\ntotal: %{time_total}\n' https://example.com/
code: 200
connect: 0.001507
tls: 0.017728
ttfb: 0.021912
total: 0.022825

connect is the TCP connection, tls is the TLS handshake, ttfb is the time until just before the first byte is transferred, and total is the time until the transfer finishes. All four values are cumulative seconds counted from the start of the request.

Because the server running curl and the site receiving the request were on the same server, the connection took only 0.0015 seconds, the TLS handshake was done at 0.0177 seconds and the first byte arrived at 0.0219 seconds. From the end of the TLS handshake to the arrival of the first byte took about 0.004 seconds. The official documentation explains that this stage includes the time the server spends producing the result.

When we ran the same command three more times, total stayed between 0.0219 and 0.0226 seconds.

🔲 curl command simulator

In the simulator below, you can type the commands so far yourself and see how the output changes with each option.

curl command simulatorcurl 7.76.1
[root@server work]#
Use the ↑↓ keys to recall commands you typed earlier.

🔢 FAQ & Recommended Content

-s hides both the progress meter and error messages. When we requested an address that does not exist, nothing was printed and only exit code 6 was left. If you specify -sS together, the progress meter stays hidden but errors such as [curl: (6) Could not resolve host] are still shown.

-k skips the step that checks whether the connection is secure, so curl sends data without checking whether the server it reached really belongs to that domain. curl checks whether the server certificate contains the domain name you connected to and whether it can be verified against the certificate store. When this error appears, check the certificate's domain name and issuer first.

According to the official documentation, when curl receives a 301, 302 or 303 response it switches the next request to GET. To keep POST, use dedicated options such as [--post301]. A method set with -X takes precedence over this conversion.

🧭 Network commands: recommended reading 9 posts · 2 references
🗂️ Network commands
📄 SSH command 📄 rsync command 📄 wget command 📄 rz · sz commands 📄 curl command
📄 host command 📄 nslookup command
📄 ip command 📄 netstat · ifconfig commands

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 8   +   8   =