Skip to content

curl

(Written by Bert Van Vreckem, https://github.com/bertvv)

The curl command is a command line tool for interacting with web servers. It can be used to make HTTP requests, download files, and more. curl is particularly useful for testing and debugging web applications and web APIs, as it allows you to see the raw HTTP requests and responses.

In this module, we will learn how to use the curl command to make HTTP requests and we will get familiar with some of its options.

In terms of functionality, curl can be compared with Postman. The latter, which has a GUI, may be more user-friendly for beginners, and "discoverability" of its featuresis one of its strengths. However, curl, being a CLI tool, can be used in scripts and automated tasks.

Other CLI tools for making HTTP requests include httpie and wget. httpie is designed to be user-friendly and has a more intuitive syntax than curl, but it lacks some of the advanced features of curl and it is less performant. wget is primarily designed for downloading files from the web, and it can also be used for "mirroring" a website.

Learning goals:

  • use the curl command to make HTTP requests.

simple http requests

Try the following:

student@linux:~$ curl 'https://icanhazdadjoke.com/'
What has a bed that you can’t sleep in? A river.

or:

student@linux:~$ curl https://httpbun.com/any
{
  "method": "GET",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any",
  "form": {},
  "data": "",
  "json": null,
  "files": {}
}

The curl command, when given a URL as argument, will send an HTTP GET request to the specified webserver and print the body of the response to standard output.

For a "normal" website, the output would consist of the HTML source code of the page (try this yourself!). In the examples above, however, the respective webservers responded with plain text and JSON data.

The website httpbun.com is a useful tool for testing HTTP requests and we will use it in many of the examples below. The URL https://httpbun.com/any is a kind of "echo" service that replies with a detailed overview of the contents of the HTTP request in JSON format. This allows us to see in detail how the curl command constructs the HTTP request and what information is sent to the server.

Another useful exercise to see exactly how curl constructs the HTTP request is to use a network traffic analyzer such as Wireshark. This allows you to see the raw HTTP request and response messages, including all headers and the body of the messages.

redirecting output

When redirected, progress information is printed to standard error. To turn off this progress information, use the -s or --silent option:

student@linux:~$ curl 'https://icanhazdadjoke.com/' > joke.txt
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95  100    95    0     0    320      0 --:--:-- --:--:-- --:--:--   319
student@linux:~$ curl -s 'https://icanhazdadjoke.com/' > joke.txt
student@linux:~$

HTTP request methods

By default, curl sends an HTTP GET request. To specify a different HTTP method, use the -X option, e.g.:

curl -X GET https://httpbun.com/any
curl -X POST https://httpbun.com/anything
curl -X PUT https://httpbun.com/anything
curl -X DELETE https://httpbun.com/anything

We show one example below, the output for the other methods is similar:

student@linux:~$ curl -X POST https://httpbun.com/anything
{
  "method": "POST",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Content-Length": "0",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any",
  "form": {},
  "data": "",
  "json": null,
  "files": {}
}

saving the result

To save the result of a curl command to a file, you can use the -o or --output option, followed by the name of the file you want to save to. For example:

student@linux:~$ curl -s -o anything.json https://httpbun.com/anything
student@linux:~$ cat anything.json
{
  "method": "GET",
  "args": {},
[...output truncated...]

Alternatively, you can use the -O or --remote-name option to save the file with the same name as it has on the server. For example:

student@linux:~$ curl -s -O https://www.google.com/robots.txt

show headers

To include the HTTP response headers in the output, use the -i or --include option. To show only the headers, use the -I or --head option. For example:

student@linux:~$ curl -i http://google.com
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-kLqF3UVItnFOPwGGRvQIKg' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Date: Fri, 12 Jun 2026 14:58:25 GMT
Expires: Sun, 12 Jul 2026 14:58:25 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

set request headers

To set a custom HTTP request header, use the -H or --header option followed by the header name and value. For example:

student@linux:~$ curl -H 'x-custom: custom header value' httpbun.com/headers
{
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "1.1 Caddy",
    "X-Custom": "custom header value"
  }
}

follow redirects

In the example above, we saw that the curl command sent a GET request to http://google.com, but the response indicated that the document has moved to http://www.google.com/. By default, curl does not follow redirects. To enable this behavior, use the -L or --location option:

student@linux:~$ curl http://google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/">here</A>.
</BODY></HTML>
student@linux:~$ curl -L http://google.com
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="nl-BE"><head>
[...output truncated...]

The last command follows the redirect and retrieves the content of the page at https://www.google.com/. It will print a considerable amount of HTML code to the terminal, which we didn't show here. Be sure to try it yourself!

POST data

To send data in the body of an HTTP request, use the -d or --data option followed by the data you want to send. For example:

student@linux:~$ curl -X POST -d 'user=admin&password=secret' https://httpbun.com/anything
{
  "method": "POST",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Content-Length": "29",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any",
  "form": {
    "password": "secret",
    "user": "admin"
  },
  "data": "",
  "json": null,
  "files": {}
}

practice: curl

In these exercises, we'll make use of the httpbun service. Be sure to check the documentation on the website to see how the different endpoints work!

If you want to investigate in even more detail how the exercises below work, you can use a network traffic analyzer such as Wireshark to see the raw HTTP traffic.

Also curl has a -v or --verbose option that shows the raw HTTP request and response messages in the terminal.

  1. Try out the different HTTP methods GET, POST, PUT, DELETE, ... in combination with the endpoints '/get', '/post', '/put', '/delete', ... and observe the output.

    What happens when you combine a method with an endpoint that doesn't match it, e.g. POST with '/get'?

  2. Use the endpoint '/svg' render the text "Hi" in an SVG image and save the result to a file (e.g. hi.svg). Hide progress output in the terminal.

  3. Use the endpoint '/redirect' to test how curl handles HTTP redirects. What's the difference in behaviour when you use the option to follow redirects or not?

    In this exercise, show the response headers in the output!

  4. Which versions of the HTTP protocol does httpbun.com support? Is there a difference in behaviour between http and https? Search the curl man-page to find how to specify the HTTP version in the request and test the results.

    You will probably need to show the response header in the output. The verbose option will even give more information.

  5. Test simple authentication with the endpoint '/basic-auth/{user}/{passwd}'. Choose a username and password. What happens when you provide the correct username and password? What happens when you provide incorrect or no credentials?

  6. Use the '/any' endpoint to see how you can send data in a POST request. Try sending form data, JSON data, and URL parameters (e.g. ?color=blue&size=medium). How does the server interpret each of these types of data?

  7. Request the main page of search engine DuckDuckGo and observe the language of the response. Use the appropriate header to request the page in a different language (e.g. nl-BE and zh-CN).

solutions: curl

1. HTTP methods

We show the POST method as an example, the other methods are similar. Most important is to observe the HTTP response code and the "method" field in the response body.

student@linux:~$ curl -X POST -i httpbun.com/post
HTTP/1.1 200 OK
Content-Length: 342
Content-Type: application/json
Date: Sun, 14 Jun 2026 10:50:28 GMT
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

{
  "method": "POST",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Content-Length": "0",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "1.1 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "http://httpbun.com/post",
  "form": {},
  "data": "",
  "json": null,
  "files": {}
}
student@linux:~$ curl -X POST -i httpbun.com/get
HTTP/1.1 405 Method Not Allowed
Access-Control-Allow-Methods: GET, OPTIONS
Allow: GET, OPTIONS
Content-Length: 0
Date: Sun, 14 Jun 2026 10:50:33 GMT
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

2. Save the result to a file

student@linux:~$ curl -s -o hi.svg "https://httpbun.com/svg/Hi"
student@linux:~$ file hi.svg 
hi.svg: SVG Scalable Vector Graphics image, ASCII text
student@linux:~$ cat hi.svg 
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
    <circle cx="50%" cy="50%" r="45%" fill="#c1a529" stroke="none" />
    <text x="50%" y="53%" text-anchor="middle" dominant-baseline="middle" font-size="36" font-family="sans-serif" fill="#222e">HI</text>
</svg>

(if you are working on a Linux system with a graphical desktop environment, you can open the file with an image viewer to see the rendered SVG image)

3. Redirects

student@linux:~$ curl -i httpbun.com/redirect/2
HTTP/1.1 302 Found
Content-Length: 174
Content-Type: text/html; charset=utf-8
Date: Sun, 14 Jun 2026 10:53:53 GMT
Location: 1
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

<!doctype html>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="1">1</a>.  If not click the link.</p>
student@linux:~$ curl -i -L httpbun.com/redirect/2
HTTP/1.1 302 Found
Content-Length: 174
Content-Type: text/html; charset=utf-8
Date: Sun, 14 Jun 2026 10:53:59 GMT
Location: 1
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

HTTP/1.1 302 Found
Content-Length: 194
Content-Type: text/html; charset=utf-8
Date: Sun, 14 Jun 2026 10:53:59 GMT
Location: ../anything
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

HTTP/1.1 200 OK
Content-Length: 318
Content-Type: application/json
Date: Sun, 14 Jun 2026 10:53:59 GMT
Via: 1.1 Caddy
X-Powered-By: httpbun/7a14f228bd735222258f685b6677ad8c564559fc

{
  "method": "GET",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "1.1 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "http://httpbun.com/anything",
  "form": {},
  "data": "",
  "json": null,
  "files": {}
}

4. HTTP versions

First, we try unencrypted HTTP. We only show the first line of the response header, which contains the HTTP version.

student@linux:~$ curl -s -I --http1.0 httpbun.com/any | head -1
HTTP/1.0 200 OK
student@linux:~$ curl -s -I --http1.1 httpbun.com/any | head -1
HTTP/1.1 200 OK
student@linux:~$ curl -s -I --http2 httpbun.com/any | head -1
HTTP/1.1 200 OK
student@linux:~$ curl -s -I --http3 httpbun.com/any | head -1
HTTP/1.1 200 OK

We see that only HTTP/1.0 and HTTP/1.1 are supported. Even if the client tries to use HTTP/2 or HTTP/3, the server falls back to HTTP/1.1.

Now we try encrypted HTTPS. We see that HTTP/2 is supported, but not HTTP/3.

student@linux:~$ curl -s -I --http1.0 https://httpbun.com/any | head -1
HTTP/1.0 200 OK
student@linux:~$ curl -s -I --http1.1 https://httpbun.com/any | head -1
HTTP/1.1 200 OK
student@linux:~$ curl -s -I --http2 https://httpbun.com/any | head -1
HTTP/2 200 
student@linux:~$ curl -s -I --http3 https://httpbun.com/any | head -1
HTTP/2 200 

In this case, the server does not fall back to HTTP/1.1 when the client tries to use HTTP/2. HTTP/3 is also not supported, and the server falls back to HTTP/2.

5. Basic Authentication

student@linux:~$ curl -i https://httpbun.com/basic-auth/admin/password123
HTTP/2 401 
alt-svc: h3=":443"; ma=2592000
date: Sun, 14 Jun 2026 11:13:11 GMT
via: 1.1 Caddy
www-authenticate: Basic realm="httpbun realm"
x-powered-by: httpbun/7a14f228bd735222258f685b6677ad8c564559fc
content-length: 0

student@linux:~$ curl -i --user admin:password123 https://httpbun.com/basic-auth/admin/password123
HTTP/2 200 
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Sun, 14 Jun 2026 11:13:30 GMT
via: 1.1 Caddy
x-powered-by: httpbun/7a14f228bd735222258f685b6677ad8c564559fc
content-length: 47

{
  "authenticated": true,
  "user": "admin"
}
student@linux:~$ curl -i --user admin:secret https://httpbun.com/basic-auth/admin/password123
HTTP/2 401 
alt-svc: h3=":443"; ma=2592000
date: Sun, 14 Jun 2026 11:13:44 GMT
via: 1.1 Caddy
www-authenticate: Basic realm="httpbun realm"
x-powered-by: httpbun/7a14f228bd735222258f685b6677ad8c564559fc
content-length: 0

6. Sending data in a POST request

Passing data using the -d option is equivalent to sending form data. The server interprets the data as form data and returns it in the "form" field of the response body.

student@linux:~$ curl -X POST -d color=blue -d size=medium https://httpbun.com/any
{
  "method": "POST",
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Content-Length": "22",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any",
  "form": {
    "color": "blue",
    "size": "medium"
  },
  "data": "",
  "json": null,
  "files": {}
}

When we specify the parameters in the URL, the server interprets them as URL parameters and returns them in the "args" field of the response body.

Warning: because the ampersand character & has a special meaning in the shell, you need to escape it with a backslash \ or put the URL in quotes!

student@linux:~$ curl -X POST 'https://httpbun.com/any?color=blue&size=medium'
{
  "method": "POST",
  "args": {
    "color": "blue",
    "size": "medium"
  },
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip",
    "Content-Length": "0",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any?color=blue&size=medium",
  "form": {},
  "data": "",
  "json": null,
  "files": {}
}

Finally, when passing JSON data with the --json option, the server interprets the data as JSON and returns it in the "json" field of the response body. The "data" field has the raw JSON data as a string.

student@linux:~$ curl -X POST --json '{ "color": "blue", "size": "medium" }' https://httpbun.com/any
{
  "method": "POST",
  "args": {},
  "headers": {
    "Accept": "application/json",
    "Accept-Encoding": "gzip",
    "Content-Length": "37",
    "Content-Type": "application/json",
    "Host": "httpbun.com",
    "User-Agent": "curl/8.14.1",
    "Via": "2.0 Caddy"
  },
  "origin": "192.0.2.137",
  "url": "https://httpbun.com/any",
  "form": {},
  "data": "{ \"color\": \"blue\", \"size\": \"medium\" }",
  "json": {
    "color": "blue",
    "size": "medium"
  },
  "files": {}
}

7. Setting headers

The output of the following commands are too long to include here, but you can try them out yourself and observe the language used in the returned HTML code.

student@linux:~$ curl https://duckduckgo.com
student@linux:~$ curl -H 'Accept-language: nl-BE' https://duckduckgo.com
student@linux:~$ curl -H 'Accept-language: zh-CN' https://duckduckgo.com

The result of the first command may depend on your system locale settings, but you can expect it to be in English. The second command should return the page in Dutch, and the third command should return the page in (simplified)Chinese.