ScreenshotNeo

BlogHow-to

How to Download a File with cURL

Learn the exact cURL commands to save, resume, verify, retry, and authenticate file downloads safely.

By the ScreenshotNeo team1 October 20267 min read

The basic command is:

curl -L -o local-name.ext "https://example.com/path/file.ext"

-o chooses the local filename. -L follows HTTP redirects. Use -O when you want cURL to use the filename from the URL path.

This guide covers filename control, redirects, resume support, retries, authentication, verification, scripting, troubleshooting, and safer production downloads.

1. Save a download with an explicit filename

Use --output (or its short form, -o) to write the response body to a file instead of printing it to the terminal. The official cURL man page defines this option as writing output to the given file.

curl -o report.pdf "https://files.example.org/reports/latest"

Always quote URLs. Characters such as ?, &, brackets, and spaces can have a meaning to your shell.

Let cURL choose the remote filename

curl -O "https://files.example.org/reports/report-2026.pdf"

-O (or --remote-name) uses the final path component of the URL. It is useful when the URL already ends in a stable filename.

Choose a filename while following redirects

curl -L -o report.pdf "https://example.org/download"

Use -o when the URL is a redirect, a route such as /download, or a signed URL whose path does not contain the name you want.

2. Follow redirects

HTTP downloads often redirect from a short URL to a CDN or an object-storage URL. cURL does not follow redirects unless you request it.

curl -L -O "https://example.com/download"

The -L (or --location) option makes cURL repeat the request at the server’s Location target for a 3xx response. See the man page entry for --location.

Redirects and credentials

cURL initially sends credentials only to the original host. Avoid --location-trusted unless you intentionally trust every redirected host, because it can send credentials across that boundary.

3. Resume an interrupted download

To continue a partial local file, use -C - (long form --continue-at -):

curl -L -C - -o archive.tar.gz "https://example.org/archive.tar.gz"

cURL checks the existing file size and asks the server for the remaining bytes. The server must support compatible byte ranges. If it does not, restart the download instead of assuming an append is safe. The Everything curl resume documentation explains the required range behavior.

Do not resume a file that may have changed at the same URL. Use a versioned URL, an ETag, or a checksum when the publisher provides one.

4. Retry transient failures

For unreliable networks, add retries and a delay:

curl -L --retry 5 --retry-delay 2 -o file.bin "https://example.org/file.bin"

--retry handles transient errors, including timeouts and HTTP 408, 429, 500, 502, 503, 504, 522, and 524 according to the current man page. cURL applies increasing backoff between attempts.

For a script, also set a maximum transfer time and fail on HTTP errors:

curl --fail --show-error --location \
  --retry 5 --retry-delay 2 --max-time 300 \
  --output file.bin "https://example.org/file.bin"
  • --fail makes HTTP 4xx/5xx responses return a failure status.
  • --show-error keeps useful diagnostics while quieting the progress meter when combined with --silent.
  • --max-time prevents a hung transfer from running forever.

5. Inspect headers before downloading

Use a HEAD request to inspect metadata without saving the body:

curl -I "https://example.org/file.zip"

Look for the status, Content-Type, Content-Length, ETag, Last-Modified, and Accept-Ranges. A HEAD response is not guaranteed to match every GET response, so treat it as a diagnostic step.

To see the complete redirect chain and request details:

curl -I -L "https://example.org/download"

6. Download protected files

Basic authentication

curl -L --user "$USER:$PASSWORD" \
  -o private.zip "https://example.org/private.zip"

Use environment variables, a protected credential file, or the service’s token mechanism instead of putting long-lived secrets directly in shell history.

Bearer tokens and custom headers

curl -L \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -o export.json "https://api.example.org/export"

Send only the headers required by the service. Never combine sensitive headers with --location-trusted unless redirected hosts are explicitly trusted.

7. Verify what you received

A successful byte transfer does not prove that the response is the expected file. Check the HTTP status and content type, then verify a size or checksum when the publisher supplies one.

curl --fail --location --output package.tar.gz "https://example.org/package.tar.gz"
sha256sum package.tar.gz

For an archive, list its contents before extracting:

tar -tzf package.tar.gz

If a server returns an HTML error page with status 200, --fail may not detect it. Inspect the file type and response headers when the source is unfamiliar.

8. Useful download options

Need Option Example
Explicit filename -o, --output curl -o report.pdf URL
Remote filename -O, --remote-name curl -O URL
Follow redirects -L, --location curl -L URL
Resume partial file -C -, --continue-at - curl -C - -o file URL
Retry transient errors --retry curl --retry 5 URL
Fail on HTTP errors --fail curl --fail URL
Headers only -I, --head curl -I URL
Set connection timeout --connect-timeout curl --connect-timeout 10 URL
Limit total time --max-time curl --max-time 300 URL
Show progress in scripts --progress-bar curl --progress-bar -O URL

9. Complete shell patterns

Reliable download to a temporary file

set -euo pipefail
url="https://example.org/releases/app.tar.gz"
tmp="app.tar.gz.part"
out="app.tar.gz"

curl --fail --show-error --location \
  --retry 5 --retry-delay 2 \
  --connect-timeout 10 --max-time 600 \
  --continue-at - --output "$tmp" "$url"
mv "$tmp" "$out"

Writing to a .part file keeps an incomplete transfer from being mistaken for a finished artifact. Rename it only after cURL exits successfully.

Download several URLs

while IFS= read -r url; do
  curl --fail --show-error --location --remote-name --remote-header-name "$url"
done < urls.txt

Review redirects before using --remote-header-name with untrusted sources, because a server can influence the suggested filename.

10. Python and Node.js equivalents

For one-off downloads, cURL is concise. Applications may prefer their language’s HTTP client so they can add logging, validation, and retry policies.

Python with requests

import requests

url = "https://example.org/file.zip"
with requests.get(url, stream=True, timeout=(10, 300)) as response:
    response.raise_for_status()
    with open("file.zip", "wb") as output:
        for chunk in response.iter_content(chunk_size=1024 * 1024):
            if chunk:
                output.write(chunk)

Node.js with fetch

import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";

const response = await fetch("https://example.org/file.zip");
if (!response.ok || !response.body) {
  throw new Error(`Download failed: ${response.status}`);
}
await pipeline(response.body, createWriteStream("file.zip"));

11. Performance, reliability, and cost

  • Stream large files instead of loading them into memory.
  • Use a nearby CDN or object-storage endpoint when the publisher provides one.
  • Resume only when the URL identifies an immutable object and the server supports ranges.
  • Use bounded retries; unlimited retries can hide a persistent outage.
  • Write to a temporary path and rename after validation.
  • Record the URL, status, byte count, checksum, and failure reason in automation logs.
  • cURL itself has no per-download service charge. Your costs come from network egress, storage, CI minutes, or the service hosting the file.

12. Troubleshooting

Symptom Likely cause Fix
Terminal fills with binary data No output file was selected Use -o filename or -O.
Downloaded file is an HTML page Redirect, login page, or application error Use -L, add required authentication, and inspect headers with -I -L.
Only a small error file is saved HTTP error returned without --fail Add --fail --show-error and check the status.
Resume starts over or fails Server lacks compatible byte-range support, or the object changed Restart from scratch or use an immutable URL that supports ranges.
Credentials leak after redirect Redirect crosses a host boundary Do not use --location-trusted; inspect the redirect chain and scope credentials.
Transfer hangs Slow or stalled connection Set --connect-timeout and --max-time; use bounded retries.
Filename is unexpected -O or server-provided filename differs from your expectation Use an explicit -o path.
SSL certificate error Wrong host, expired certificate, or incomplete trust store Fix the URL or trust store. Keep certificate verification enabled; do not use -k as a routine workaround.
Permission denied writing output Directory is not writable Choose a writable directory or fix its permissions.

13. Or skip the browser setup

If the file you need is a website screenshot or PDF, ScreenshotNeo provides a GET endpoint instead of requiring you to install and operate a browser.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options and response headers. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether it was billed. The service also offers an MCP server so Claude, Cursor, and other MCP clients can take screenshots, inspect pages, and capture PDFs. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with 1,000 screenshots per month without a card.

14. FAQ

What is the difference between -o and -O?

-o takes the exact local path you provide. -O derives the name from the URL path.

Does cURL follow redirects automatically?

No. Add -L or --location.

Can every download be resumed?

No. The server must support compatible byte-range requests, and the remote object must remain unchanged.

How do I prevent an incomplete file from being used?

Download to a temporary filename, validate the result, then rename it into place.

Should I disable TLS verification when a download fails?

No. Diagnose the certificate, hostname, or trust-store problem and keep verification enabled.