Most of us learned cURL the same way. Somebody pasted a command into a chat, it worked, and we have been copying variations of it ever since. That gets you surprisingly far. It also means a lot of developers are still using about five percent of a tool that has been installed on their machine for years.
I spend a good part of my week poking at APIs that sit behind mobile apps: login endpoints, push notification services, webhooks that fire at the worst possible moment. cURL is the first thing I reach for, long before Postman or a test suite. These are the tricks I actually use, in roughly the order I wish someone had shown them to me.
Before you type anything, open the Network tab in your browser’s developer tools, right-click the request you care about and choose Copy as cURL. You get the full request with every header and cookie attached, ready to paste into a terminal.
This is the fastest way to reproduce a bug that “only happens in the app”. Paste the command, confirm it fails the same way, then start deleting headers one at a time until it stops failing. Whatever you removed last is your culprit.
“The API is slow” is not a bug report. The -w option turns it into one by printing timing data after the transfer finishes:
curl -s -o /dev/null -w "dns: %{time_namelookup}s\nconnect: %{time_connect}s\ntls: %{time_appconnect}s\nfirst byte: %{time_starttransfer}s\ntotal: %{time_total}s\n" https://api.example.com/v1/healthEach value is measured from the start of the request, so the gaps between them tell the story. A big jump before connect points at DNS. A big jump between tls and first byte means your server took its time thinking. I keep this one in a shell alias because I use it almost daily.
For years, posting JSON meant writing out a Content-Type header, an Accept header and a -d payload. Since version 7.82.0, cURL has a shortcut that does all three:
curl --json '{"device":"android","build":412}' https://api.example.com/v1/sessionsIt reads from a file too, with --json @payload.json. If you are on an older cURL, the long form still works, but check curl --version first. You may be more up to date than you think.
Anyone who has tried to pass an email address or a search phrase in a URL knows the pain of percent-encoding by hand. Let cURL do it:
curl -G https://api.example.com/v1/search --data-urlencode "q=push notifications & badges" --data-urlencode "lang=en"The -G flag tells cURL to append the data to the URL as a query string instead of sending it as a POST body. The spaces and the ampersand get encoded for you, and the command stays readable.
You have a new backend server ready, but the domain still points at the old one. Editing your hosts file works, right up until you forget to change it back. --resolve is the cleaner option:
curl --resolve api.example.com:443:203.0.113.10 https://api.example.com/v1/healthcURL connects to the IP you gave it while still sending the correct hostname, so TLS certificates and virtual hosts behave exactly as they will in production. It applies to that one command and nothing else on your machine.
Sooner or later your app does something different depending on where the request comes from. Prices change by country, a payment provider blocks a region, a CDN serves stale content in one market only. You cannot debug that from your desk unless your request can leave from somewhere else.
That is what the -x flag is for:
curl -x "http://USERNAME:PASSWORD@proxy.example.com:8080" https://api.example.com/v1/pricingTwo things trip people up here. First, quote the whole proxy string, because passwords tend to contain characters your shell wants to interpret. Second, if you are on a SOCKS5 proxy, socks5h:// makes the proxy resolve the hostname, while plain socks5:// resolves it on your own machine. For geo testing you almost always want the first one.
There is more to it than one flag, including environment variables, authentication quirks and a permanent setup in .curlrc. This guide to using cURL with a proxy walks through all of it with copy-ready commands, and it is the one I point teammates to when their proxy returns a 407 and they cannot work out why.
Here is a fun way to break a deployment script: cURL exits with code 0 even when the server answers with a 500. As far as cURL is concerned, it delivered a response, so the job is done.
Add --fail-with-body and HTTP errors of 400 and above produce a non-zero exit code, while you still get to see the error body the server sent back:
curl --fail-with-body -sS https://api.example.com/v1/deploy-hookThe -sS pair is worth memorizing as well. It hides the progress meter but keeps real error messages, which is exactly what you want in CI logs.
Networks hiccup. Instead of wrapping cURL in a bash loop with a sleep, use the retry options that are already built in:
curl --retry 5 --retry-delay 2 --retry-connrefused --max-time 20 https://api.example.com/v1/syncBy default cURL retries only on errors it considers temporary, such as timeouts and responses like 429, 502, 503 and 504. That is sensible behavior, since retrying a 404 five times will not make the page appear. Just be careful with requests that are not safe to repeat. Retrying a payment call is a good way to meet your support team.
Out of the box, cURL will wait a very long time for a server that never answers. In a cron job or a pipeline, that means a hung process nobody notices until Monday.
curl --connect-timeout 5 --max-time 30 https://api.example.com/v1/report--connect-timeout covers the connection phase, and --max-time caps the entire operation. I add both to every script by habit now. It is the cheapest reliability improvement I know of.
Pasting an API token straight into a command puts it in your shell history, and possibly in a process listing someone else can read. A config file avoids that:
# api.conf
header = "Authorization: Bearer YOUR_TOKEN"
connect-timeout = 5
max-time = 30curl -K api.conf https://api.example.com/v1/users/meLock the file down with restrictive permissions, add it to .gitignore, and you have a reusable setup per environment. One file for staging, one for production, and far fewer chances of pasting a live token into a screen share.
If you work in Windows PowerShell 5.1, typing curl does not run cURL at all. It runs an alias for Invoke-WebRequest, which accepts none of the flags above and produces some truly confusing errors. Type curl.exe instead and everything in this article works as written.
None of these tricks are obscure. They are all in the manual, which is long enough that few people read past the first screen. Pick two or three that match the problems you hit most often, put them in an alias or a config file, and let muscle memory do the rest.
The timing breakdown and the proxy flag are the two that changed how I debug. One tells you where a request is slow, the other shows you what your users in another country are really seeing. Between them, they have saved me from more than one late night spent guessing.
Coloring is often described as a simple pastime, but good coloring-page design involves real visual…
A Scala service may run without any obvious issues in staging and then struggle when…
Why the Warning Signs Get Misread Ask a mobile team why it's still on a…
A mobile app project can collect many kinds of visuals: screenshots, onboarding illustrations, short videos,…
Introduction Ask any creator who's tried to build a series with AI video, and they'll…
Mark the calendar. Call of Duty: Modern Warfare 4 releases worldwide on October 23, 2026,…