Struggling with poor network performance?


The cURL program is widely available across many different platforms, which makes it an obvious choice for network testing. It is simple, scriptable, and flexible – which is why it is so powerful. It supports many protocols, but we are going to focus on HTTP in this article.
The basic syntax for a cURL command is pretty straightforward – just add the destination URL:

$ curl

For this simple command, curl will return the result. That usually means a bunch of HTML will be sent to your console. For the example command above, we get the following:

$ curl
301 Moved
<h1>301 Moved</h1>
The document has moved
<a href="">here</a>.

Remember, curl is not a browser, so by default it doesn’t follow redirects. It simply executes the single command that you gave it (in this case, an HTTP GET). You can output the request headers by adding a -i flag to your command:

$ curl -i
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=UTF-8
Date: Thu, 10 Aug 2017 23:29:44 GMT
Expires: Sat, 09 Sep 2017 23:29:44 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN

301 Moved
<h1>301 Moved</h1>
The document has moved
<a href=””>here</a>.

If you do want curl to follow the redirect, just add the -L parameter.

Now, this may be interesting for one-off manual tests, but probably not for automated testing. Fortunately, curl allows us to customize and format the command output.

Output Formatting

Curl has a -w flag which tells curl to output a certain string of information after the transfer has completed. Here is a list of available variables:

  • content_type
  • filename_effective
  • ftp_entry_path
  • http_code
  • http_connect
  • http_version
  • local_ip
  • local_port
  • num_connects
  • num_redirects
  • proxy_ssl_verify_result
  • redirect_url
  • remote_ip
  • remote_port
  • scheme
  • size_download
  • size_header
  • size_request
  • size_upload
  • speed_download
  • speed_upload
  • ssl_verify_result
  • time_appconnect
  • time_connect
  • time_namelookup
  • time_pretransfer
  • time_redirect
  • time_starttransfer
  • time_total
  • url_effective

The man page for curl contains more detailed information about each variable (including units, etc.).

If we add a few output variables to our original example, we get the following:

$ curl -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n"
301 Moved
<h1>301 Moved</h1>
The document has moved
<a href="">here</a>.

Notice the -w parameter allows us to add in additional characters beyond simply the provided variables. This means we can have a nicely formatted CSV output at the end of our command.

But in a performance script, we probably wouldn’t want the actual page content. For that, we can add a -o flag and send the output to /dev/null (a.k.a., oblivion). Observe:

$ curl -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 219 100 219 0 0 1256 0 --:--:-- --:--:-- --:--:-- 1258

Wait! That got rid of the content, but it replaced it with a progress table. That table is very useful when you are watching a long-running transfer. But not helpful for our automated scripting scenario. Fortunately, the -s (“silent”) option tells curl to keep that progress to itself.

$ curl -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null -s

Perfect! Now we can append that output onto our results file:

$ curl -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null -s >> myResults.csv

Dynamic URLs

While single URLs can be useful, more often we have groups of URLs we want to test. Luckily, cURL has some built-in methods for providing multiple variations into a single command. Using brackets [] for ranges, and curly braces {} for sets, we can tell curl to do some interesting things. For example:

$ curl[1985-1990] -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null -s

We just searched for every year from 1985 – 1990 with a single curl command. By specifying this range in the URL, curl simply goes through each value one at a time. We can also use brackets to create a list of queries:

$ curl "{jurassic%20park,jumanji,armageddon}" -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null -s

At this point, you may have noticed that we are getting 403 responses from Google. That’s because we don’t have a user agent, since we aren’t using a browser. If we add the –user-agent option, we get 200s instead:

$ curl "{jurassic%20park,jumanji,armageddon}" -w "%{time_connect},%{time_total},%{speed_download},%{http_code},%{size_download},%{url_effective}\n" -o /dev/null -s --user-agent Tutorial

Note that in most cases, you’ll probably want to use a real user agent string, but this works for our purposes.


So far we’ve only looked at downloads. What about uploads? The main difference is you need to specify a file to upload. This is done with the -F parameter. And of course, your URL needs to be one that accepts uploads. Let’s look at an example:

$ curl -F file=@test.file
{"status":"OK","message":"file uploaded","$_FILES":{"file":{"name":"test.file","type":"application\/octet-stream","tmp_name":"\/tmp\/phpMHNoBs","error":0,"size":22}}}

Note that the output you get will vary depending on what page you are hitting. In this case, we got a json object with some details about the upload. If we don’t want this output, we can go back to our output variables from earlier and get a nice CSV output (don’t forget to use speed_upload and size_upload now):

$ curl -F file=@test.file -w "%{time_connect},%{time_total},%{speed_upload},%{http_code},%{size_upload},%{url_effective}\n" -o "/dev/null" -s

Voila! We have uploaded a file with curl. But before we move on, just a quick note on -F : this emulates a form submission. We specified “file” as the name of the form-field we were filling with test.file. If it doesn’t match the form-field on the page, you will likely get errors. Make sure to read the man page for more details on form submission.


Sometimes transfers take a long time. Sometimes servers are unavailable. Default timeouts are often as high as 5 minutes. Fortunately, we can specify our own timeout values for curl to follow.

$ curl -F file=@server1.pcap.gz -w "%{time_connect},%{time_total},%{speed_upload},%{http_code},%{size_upload},%{url_effective}\n" -o "/dev/null" -s --connect-timeout 15 --max-time 30

This is useful when you are dealing with larger files, or when you’d rather just timeout and move on to the next test. The two parameters above, –connect-timeout and –max-time, are quite useful. However, there are other time related parameters as well, such as:


More details about each of these parameters can be found on the man page.

Click here for part 2, where we go through some sample test scripts!