How to Use curl to Download a File (2026 Guide)

You're usually not searching for “use curl to download a file” because you want theory. You have a URL, a script, and a task that needs to finish without babysitting. Sometimes it's a public ZIP. More often it's a protected API response, a CSV export, or a scheduled ingestion job that breaks the moment a redirect, timeout, or auth header gets involved.
That's where most basic curl examples stop being useful. They show one happy-path command and skip the parts that matter in production: naming files correctly, following redirects, resuming interrupted transfers, passing Bearer tokens, and verifying that the file you got is precisely the file you meant to get.
This guide is the version I'd give a junior developer who's about to wire curl into a real script. It covers the obvious commands, but it also covers the stuff that causes failed jobs at 2 AM.
Why curl Still Dominates Command-Line Downloads
curl has been around long enough that some developers treat it like old plumbing. That's a mistake. It was originally released on April 8, 1998 by Daniel Stenberg, and it has become the de facto standard for command-line downloads and data transfer, powering over 80% of the world's web servers according to a 2023 Cloudflare analysis referenced here. It also supports 30+ protocols, including HTTP, HTTPS, FTP, and SFTP, which is why it keeps showing up everywhere from local shells to CI runners.
That ubiquity matters. A download command isn't useful only when it works on your laptop. It needs to work in GitHub Actions, in a Docker container, on a Linux VM, on macOS, and sometimes on a Windows box you didn't want to touch in the first place. curl keeps winning because it's scriptable, predictable, and already present in most developer workflows.
It solves more than file downloads
A lot of people first meet curl as “the tool that fetches a file.” In practice, it sits in the middle of API work, smoke tests, deployment scripts, ingestion jobs, and operational debugging.
You can use one tool to:
Download a release artifact from an HTTPS endpoint
Pull JSON from an authenticated API
Inspect headers before writing a script
Resume a partial transfer after a network drop
Measure transfer details when performance matters
Practical rule: If the task involves a URL and needs to be automated,
curlis usually the first tool worth trying.
That's also why teams building data-heavy products keep relying on it. In data ingestion work, simple shell commands often beat heavier client code for one-off jobs, cron tasks, and pipeline glue. If you follow developer workflows in places like the RealtyAPI.io engineering blog, you'll see the same pattern repeatedly: small, composable commands that are easy to inspect and easy to automate.
Why it still matters in 2026
Developers don't need another GUI for “download file.” They need a command that can authenticate, retry, fail clearly, and fit inside a script without drama.
That's what curl gives you. The skill isn't memorizing every flag. It's knowing which few flags make a download reliable.
The Fundamentals of Downloading a File
Most failed curl downloads come from one of three mistakes. People print file contents into the terminal by accident, save with the wrong name, or forget to follow redirects.
What curl does by default
If you run curl with just a URL, it writes the response body to standard output.
curl https://example.com/file.zip
That's fine for inspecting text responses. It's the wrong default for binary downloads. If the URL points to a ZIP, image, or PDF, your terminal fills with garbage and you still don't have a usable file.
Use one of the output flags instead.
Essential Download Flags Compared
Flag | Behavior | Example Usage | Best For |
|---|---|---|---|
| Saves using the remote filename |
| Quick downloads when the server filename is correct |
| Saves to a local filename or path you choose |
| Scripts, organized output paths, predictable naming |
| Follows redirects |
| Endpoints that redirect through CDNs, signed URLs, or moved resources |
The three commands you'll use constantly
Use -O when the remote filename is what you want
curl -O https://example.com/releases/app.tar.gz
This tells curl to save the file locally as app.tar.gz. It's the fastest option when you trust the remote name and don't care where the file lands beyond the current directory.
Use -o when your script needs a stable destination
curl -o backups/nightly-export.csv https://example.com/exports/current.csv
This is usually the better choice in automation. Your downstream steps shouldn't guess what the filename will be. Give the file a path and a name you control.
If another script reads the output later, prefer
-o. Predictable filenames beat “whatever the server called it.”
Use -L when the URL isn't the final file location
A lot of download links redirect. Release assets, cloud storage links, signed download endpoints, and API-driven exports often return a 3xx response before the actual file transfer starts.
curl -L -O https://example.com/download/latest
If you skip -L, curl may save the redirect response instead of the file you wanted, or it may stop without following the chain.
A safe baseline for ordinary file downloads is:
curl -L -O https://example.com/file.zip
If you want a custom name instead:
curl -L -o myfile.zip https://example.com/file.zip
That small difference between -O and -o matters more than people think. One preserves the server's filename. The other makes your script maintainable.
Handling Interruptions and Unreliable Networks
Big downloads fail in boring ways. Wi-Fi drops. A VPN reconnects. The server stalls. Your CI job gets flaky for no obvious reason.
When the file is large, restarting from byte zero is the worst possible behavior.

For large data exports, this isn't edge-case paranoia. For PropTech workloads aggregating 10GB+ CSV exports, curl range requests can deliver 99%+ success rates on unstable connections with a 95% reduction in re-download overhead, and adding --retry 5 --retry-delay 10 raised success to 98.7% versus 82% without in the benchmark cited by DigitalOcean's curl workflow guide.
Resume instead of restarting
The flag you want is -C -.
curl -L -C - -O https://example.com/large-dataset.csv
-C - tells curl to continue from the current local file size if the server supports resuming. If the download stopped at most of the file, this saves time and bandwidth immediately.
For scripts, this is the default I'd use for any large artifact:
curl -L -C - -o data/export.csv https://example.com/exports/export.csv
Add retries so scripts survive the real world
Retry logic shouldn't live only in your scheduler. Put it in the command too.
curl -L -C - --retry 5 --retry-delay 10 -o data/export.csv https://example.com/exports/export.csv
That handles transient failures without forcing your wrapper script to reimplement common behavior. If you're pulling from an API with usage constraints, pair this with the provider's published guidance, such as the rate limit documentation at RealtyAPI.io, so you don't turn recovery into accidental hammering.
A practical pattern for batch jobs looks like this:
Resume enabled:
-C -Redirects enabled:
-LRetries configured:
--retry 5 --retry-delay 10Explicit output path:
-o path/file.ext
Use range requests when the file is huge
If the server supports byte ranges, you can request only part of a file.
curl -L -r 0-1048575 -o first-megabyte.bin https://example.com/huge-file.bin
That's useful for testing, partial retrieval, or chunked workflows. Before depending on it heavily, inspect headers and confirm the server behaves the way you expect.
This walkthrough is worth watching if you want to see the mechanics in action:
Don't treat a successful single download as proof that your command is production-ready. Production-ready means it resumes, retries, and exits predictably.
Downloading from Secured and Authenticated Endpoints
Public files are the easy case. Most useful files aren't public. They sit behind login, API tokens, cookies, or a mix of all three.
That's why “use curl to download a file” quickly turns into “how do I send the right headers and keep the request valid.”

Most guides stay stuck on username and password. That's incomplete. Bearer tokens and cookie jars are essential for 80%+ of protected resources, and 40% of API downloads fail without header inspection first because servers often require headers like Accept: application/json, according to the KodeKloud article cited here.
Basic auth still exists
Some internal tools and legacy endpoints still use HTTP Basic Auth. curl supports it cleanly with -u.
curl -u username:password -L -O https://example.com/private/report.csv
That works, but it's rarely the main pattern in modern SaaS APIs.
Two cautions:
Don't hardcode secrets into shared scripts or committed files.
Prefer environment variables when you need credentials in automation.
Example:
curl -u "$API_USER:$API_PASS" -L -o report.csv https://example.com/private/report.csv
Bearer tokens are the normal case now
For modern APIs, you'll usually pass an Authorization header.
curl -L \
-H "Authorization: Bearer $TOKEN" \
-o listings.json \
https://api.example.com/properties
That's the pattern you'll use for protected downloads, data exports, and authenticated JSON endpoints. If the endpoint serves data rather than a literal downloadable file, the output still belongs in a file so other tools can process it reliably.
For example:
curl -L \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-o data/properties.json \
https://api.example.com/properties?city=miami
If you're integrating a real estate data service, read the API docs first instead of guessing header conventions. A developer-oriented reference like the RealtyAPI.io introduction docs shows the kind of auth and request structure you should expect from a modern API.
One habit prevents a lot of wasted time: inspect the endpoint before scripting the full download.
Use -I to check headers:
curl -I -H "Authorization: Bearer $TOKEN" https://api.example.com/properties/export
That helps you see redirects, content type, auth challenges, and whether you're hitting a file endpoint or an API response wrapper.
Cookies and header inspection matter more than people think
Some endpoints rely on session cookies rather than a static token. curl can save and reuse cookies with -c and -b.
Login and save cookies:
curl -c cookies.txt -d "username=user&password=pass" https://example.com/login
Use saved cookies for the download:
curl -b cookies.txt -L -O https://example.com/secure/export.zip
That matters when you're dealing with portals, dashboards, and temporary session-backed downloads.
A few practical rules help here:
Check
Content-Typefirst: Don't assume you're getting a file. You may be getting JSON error output.Add
Acceptdeliberately: Some APIs won't serve the expected representation without it.Combine auth with resilience: Protected exports can still be resumed and retried.
Example:
curl -L -C - \
--retry 5 --retry-delay 10 \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-o export.json \
https://api.example.com/exports/latest
If your command authenticates correctly but still fails, don't keep tweaking flags blindly. Inspect headers, confirm the response type, and verify the server is returning what your script assumes.
Advanced Techniques for Scripting and Automation
A scheduled export that succeeds at 2 a.m. and hands your next job an HTML login page is still a failure. In scripts, curl needs to do more than fetch bytes. It needs to produce predictable output, expose enough metadata for monitoring, and prove the downloaded file is the one your pipeline expected.

Make output script-friendly
Interactive defaults get in the way once curl runs under cron, CI, or a data ingestion job. Suppress progress, keep errors visible, and make the exit code meaningful.
curl -s -S -L -o data.json https://example.com/data.json
-s removes the progress meter. -S still prints errors. That combination keeps logs clean without hiding failures.
For automation, I also want the HTTP status and transfer timing in the logs, separate from the response body:
status=$(curl -s -S -L \
-o data.json \
--write-out "%{http_code}" \
https://example.com/data.json)
if [ "$status" -ne 200 ]; then
echo "download failed with HTTP $status" >&2
exit 1
fi
For protected API exports, that check matters because a 401, 403, or 429 usually means your script needs to refresh credentials, slow down, or handle rate limits. If you need a quick reference for API responses, RealtyAPI's HTTP status code documentation for API integrations is the kind of table worth keeping nearby while you wire this into a job runner.
If you want transfer metrics in build logs, --write-out is the cleanest option:
curl -s -o /dev/null \
--write-out "%{size_download} bytes at %{speed_download} B/s in %{time_total}s\n" \
https://example.com/file.zip
That pattern gives you data your monitoring can parse without mixing it into the downloaded file.
Verify what you downloaded
HTTP success only tells you the server returned something. It does not tell you the artifact is complete, unmodified, or even the right file.
A checksum step catches all three problems early:
curl -O https://example.com/file.zip && \
curl -O https://example.com/file.zip.sha256 && \
sha256sum -c file.zip.sha256
If you ingest nightly property exports, market snapshots, or document bundles from a Bearer-token API, checksum verification should happen before parsing starts. Otherwise one bad file can poison the rest of the pipeline.
A simple Bash version looks like this:
#!/usr/bin/env bash
set -e
curl -s -S -L -O https://example.com/releases/build.tar.gz
curl -s -S -L -O https://example.com/releases/build.tar.gz.sha256
sha256sum -c build.tar.gz.sha256
That fails fast, which is what you want in automation.
Build commands your pipeline can trust
A lot of teams stop at “download completed.” That is too shallow for modern workflows, especially when the file comes from an authenticated endpoint and feeds another system.
This pattern is more realistic:
#!/usr/bin/env bash
set -euo pipefail
outfile="properties.json"
status=$(curl -s -S -L \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-o "$outfile" \
--write-out "%{http_code}" \
https://api.example.com/properties/export)
[ "$status" = "200" ] || {
echo "export request failed with HTTP $status" >&2
exit 1
}
jq empty "$outfile" >/dev/null
That last line matters. If the API is supposed to return JSON and jq fails, the script should stop there. In practice, this catches expired tokens, proxy error pages, and “temporary maintenance” responses that still arrived with a 200.
Download many files without writing terrible loops
Batch jobs get messy fast if every file is handled by a hand-built shell loop. Start with the features curl already gives you.
For predictable file sequences, URL globbing is enough:
curl -O https://example.com/datasets/property[001-005].csv
For independent files, parallel transfers can shorten ingestion windows:
curl --parallel \
-O https://example.com/datasets/property001.csv \
-O https://example.com/datasets/property002.csv \
-O https://example.com/datasets/property003.csv
Use that carefully. Parallel downloads are good for sharded datasets, image collections, and split archives. They are less useful if the upstream API rate-limits aggressively or ties every request to the same short-lived token.
A practical rule set helps:
Single output file for another job: use
-L -oand capture%{http_code}Authenticated API export: send the
Authorization: Bearerheader and validate the response formatPublished artifact with checksum: verify it before extraction or parsing
Many predictable files: use globbing or
--parallelbefore writing custom loop logic
The finish line in automation is straightforward. The right file arrived, it passed verification, and your script can prove both.
Common Pitfalls and Essential Troubleshooting
Most curl problems aren't mysterious. They're just hidden behind bad assumptions and poor visibility.
Windows PowerShell catches people immediately
On Windows, the default curl in PowerShell is an alias, and that causes 100% of standard curl commands to fail in the scenario described by IPRoyal's curl troubleshooting article. If you're on Windows and your command makes no sense, call the actual binary directly:
curl.exe -L -O https://example.com/file.zip
That one change saves a lot of confusion.
SSL errors need diagnosis first
The same source notes that 28% of download failures stem from SSL mismatches. Don't jump straight to -k. First, inspect what's happening.
curl -v https://example.com/file.zip
Verbose mode shows the request, the response headers, the TLS conversation, and any redirect behavior. That's how you tell whether the problem is a certificate issue, a redirect to a different host, or an auth failure disguised as something else.
If you're in a trusted internal environment and you know the certificate issue is expected, -k can bypass verification:
curl -k -L -O https://example.com/file.zip
Use that carefully. It's a workaround, not a default.
Verbose mode is your best debugger
When a download fails, people often swap flags randomly. That wastes time. -v gives you the evidence you need.
Use it to inspect:
Redirects that require
-LHeaders that reveal auth problems
Content types that show you downloaded JSON instead of a ZIP
Server responses that explain why a request was rejected
If your script handles API downloads, keep the provider's response behavior close at hand. A reference like the RealtyAPI.io status code documentation is useful because it tells you what different classes of responses mean in an automated workflow.
A failing
curlcommand usually isn't broken. It's telling you something.-vis how you hear it clearly.
If you're building real estate ingestion, listing search, market monitoring, or export pipelines, RealtyAPI.io gives you a developer-first data layer with REST, GraphQL, webhooks, and production-ready reliability. You can get an API key quickly, test with the free tier, and move from one-off curl commands to a proper data workflow without rebuilding your stack around scraping glue.