How to Upload Files with cURL — API Examples & One-Liners
cURL is the fastest way to upload a file from the command line. Whether you are testing a REST API, scripting a deployment pipeline, or just need to get a file onto a server, a single cURL command can handle it. This guide covers everything from basic uploads to piping data from stdin, with copy-pasteable one-liners for every scenario.
Basic cURL File Upload
The simplest file upload uses the -F flag to send a file as multipart/form-data:
curl -X POST https://httpbin.org/post \
-F "file=@photo.png"
The @ prefix tells cURL to read the contents of photo.png from disk. Without it, cURL sends the literal string "photo.png" as text. The -F flag automatically sets the Content-Type header to multipart/form-data and generates the boundary string that separates parts in the request body.
You can also specify the MIME type explicitly if cURL does not detect it correctly:
curl -X POST https://httpbin.org/post \
-F "file=@report.pdf;type=application/pdf"
Uploading with Headers and Authentication
Most file upload APIs require authentication. The two most common patterns are API key headers and Bearer tokens. Use the -H flag to attach them:
API key header:
curl -X POST https://api.example.com/upload \
-H "X-API-Key: sk_live_abc123" \
-F "file=@invoice.pdf"
Bearer token:
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \
-F "file=@invoice.pdf"
You can combine multiple -H flags to send additional headers, like a custom filename or metadata:
curl -X POST https://api.example.com/upload \
-H "X-API-Key: sk_live_abc123" \
-H "X-Custom-Filename: quarterly-report.pdf" \
-F "file=@report.pdf"
Uploading to FilePost API
FilePost is a file upload REST API that takes one request and gives you back a permanent CDN URL. Here is a complete working example:
curl -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@screenshot.png"
Response:
{
"url": "https://cdn.filepost.dev/file/filepost/uploads/a1/a1b2c3.png",
"file_id": "a1b2c3d4e5f6",
"size": 48210
}
That is it. The url field is a permanent, publicly accessible CDN link. You can use it in HTML, Markdown, Slack messages, or store it in your database. No bucket configuration, no IAM roles, no SDK installation required.
To save the URL directly to a variable in bash:
FILE_URL=$(curl -s -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@screenshot.png" | jq -r '.url')
echo "$FILE_URL"
Uploading Multiple Files
If you need to upload a batch of files, loop through them in bash:
for file in ./images/*.png; do
echo "Uploading $file..."
curl -s -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@$file"
echo ""
done
For parallel uploads that finish faster, use xargs:
ls ./images/*.png | xargs -P 4 -I {} curl -s -X POST \
https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@{}"
The -P 4 flag runs up to four uploads concurrently. Adjust the number based on your connection speed and the API's rate limits.
Uploading from a URL or Pipe
You do not always have a file on disk. cURL can upload data piped from another command or downloaded from a URL.
Pipe from another command:
cat database_dump.sql | curl -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@-;filename=backup.sql"
The @- tells cURL to read from stdin. The ;filename=backup.sql part sets the filename in the multipart request since there is no real file to infer it from.
Download and re-upload in one line:
curl -s https://example.com/image.jpg | curl -X POST \
https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@-;filename=image.jpg"
Upload a tarball on the fly:
tar czf - ./project/ | curl -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@-;filename=project.tar.gz"
This compresses the ./project/ directory and uploads it without writing a temporary file to disk.
cURL Flags Explained
Here is a reference of the flags you will use most often when uploading files:
-F "file=@path"— Send a file asmultipart/form-data. The@prefix reads from disk. This is the core flag for file uploads.-H "Header: value"— Add a custom HTTP header. Use this for authentication keys, Bearer tokens, or any custom metadata.-X POST— Set the HTTP method. Technically optional when using-F(cURL defaults to POST), but including it makes the command self-documenting.-o output.json— Write the response body to a file instead of stdout. Useful for saving the API response.-s— Silent mode. Suppresses the progress bar and error messages. Essential for scripting.-S— Used with-sto still show errors. The combination-sShides progress but shows failures.-w "%{http_code}"— Print the HTTP status code after the response. Useful for checking if the upload succeeded.--max-time 60— Set a maximum time in seconds for the entire operation. Prevents uploads from hanging indefinitely on slow connections.-v— Verbose mode. Shows the full HTTP request and response headers. Invaluable for debugging upload problems.
A production-ready one-liner combining several of these flags:
curl -sS -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: your_api_key_here" \
-F "file=@report.pdf" \
--max-time 120 \
-w "\nHTTP Status: %{http_code}\n"
Troubleshooting Common Errors
400 Bad Request: Malformed multipart body
This almost always means you set the Content-Type header manually. When you use -F, cURL generates the multipart boundary and embeds it in the Content-Type header automatically. If you override it with -H "Content-Type: multipart/form-data", the boundary will be missing and the server cannot parse the body. The fix is simple: remove any -H "Content-Type: ..." flag from your command.
401 Unauthorized: Invalid API key
Double-check the header name. Some APIs use X-API-Key, others use Authorization: Bearer. Also watch for trailing whitespace or newlines if you are reading the key from a file:
# Wrong: includes a trailing newline
API_KEY=$(cat api_key.txt)
# Right: strip whitespace
API_KEY=$(cat api_key.txt | tr -d '[:space:]')
413 Request Entity Too Large
The file exceeds the server's upload limit. Check the API documentation for maximum file sizes. If you are behind a reverse proxy like Nginx, check its client_max_body_size directive, which defaults to 1MB.
Connection timeout or broken pipe
Large files on slow connections can exceed default timeout values. Use --max-time to set a generous limit. If the connection drops mid-transfer, the server likely closed it early, often because the file exceeded the size limit before the upload finished. Check for 413 errors with -v to see the server's response headers.
curl: (26) Failed to open/read local data from file
The file path after @ does not exist or is not readable. Verify the path and check file permissions:
ls -la ./photo.png
# If permission denied:
chmod 644 ./photo.png
Frequently Asked Questions
How do I upload a file with cURL?
Use the -F flag with the @ prefix to read a file from disk: curl -X POST https://filepost.dev/v1/upload -H "X-API-Key: your_key" -F "file=@photo.png". The -F flag handles multipart encoding and sets the correct Content-Type header automatically.
What is the difference between -F and -d in cURL?
The -F flag sends data as multipart/form-data, which is the content type required for file uploads. It can transmit binary data. The -d flag sends data as application/x-www-form-urlencoded, which is designed for text key-value pairs like form fields. If you use -d to send a file, the binary data will be corrupted.
Can I upload multiple files with a single cURL command?
It depends on the API. Some endpoints accept multiple -F fields: curl -F "file1=@a.png" -F "file2=@b.png" https://api.example.com/upload. If the API only accepts one file per request, use a bash loop or xargs to upload them sequentially or in parallel.
Why does my cURL file upload return a 400 error?
The most common cause is manually setting the Content-Type header with -H. When you use -F, cURL generates the multipart boundary string and includes it in the Content-Type header. If you override that header, the boundary will not match the body, and the server rejects the request. Remove the -H "Content-Type: ..." flag and let cURL handle it.
Upload Your First File in 30 Seconds
FilePost gives you 300 free uploads per month with a single API call. No buckets, no IAM roles, no SDK required.
Get Your Free API Key