Large HTTP uploads can fail late when an API, proxy, or object store rejects the request only after the client has started sending the body. Controlling Expect: 100-continue in cURL makes that behavior predictable and can prevent wasted upload time on requests that were going to be denied anyway.
On HTTP/1.1, cURL automatically adds Expect: 100-continue to PUT uploads and to POST requests with bodies larger than one megabyte. --header "Expect:" removes the header, --header "Expect: 100-continue" forces it on smaller requests, and --expect100-timeout changes how long cURL waits before sending the body anyway.
The header matters only on HTTP/1.1. Broken gateways and proxies sometimes ignore or delay the provisional response, so the right policy depends on the upstream path: keep the header when early rejection saves bandwidth, remove it when the extra wait adds a pointless stall, and shorten the timeout when the server does answer with 100 Continue but later than cURL's default one-second pause.
Related: How to use HTTP/2 in cURL
Related: How to use HTTP/3 with cURL
$ curl --silent --show-error --verbose --http1.1 \ --upload-file ./release-bundle-2026.04.22.tar.gz \ --output /dev/null \ "https://uploads-api.example.net/v1/releases/release-bundle-2026.04.22.tar.gz" > PUT /v1/releases/release-bundle-2026.04.22.tar.gz HTTP/1.1 > Host: uploads-api.example.net > User-Agent: curl/8.7.1 > Accept: */* > Content-Length: 1048704 > Expect: 100-continue < HTTP/1.1 100 Continue * upload completely sent off: 1048704 bytes < HTTP/1.1 201 Created
On HTTP/1.1, cURL adds the header automatically on PUT uploads and on POST bodies larger than one megabyte.
Related: How to upload files with cURL
$ curl --silent --show-error --verbose --http1.1 \ --header "Expect:" \ --upload-file ./release-bundle-2026.04.22.tar.gz \ --output /dev/null \ "https://uploads-api.example.net/v1/releases/release-bundle-2026.04.22.tar.gz" > PUT /v1/releases/release-bundle-2026.04.22.tar.gz HTTP/1.1 > Host: uploads-api.example.net > User-Agent: curl/8.7.1 > Accept: */* > Content-Length: 1048704 * upload completely sent off: 1048704 bytes < HTTP/1.1 201 Created
Removing the header means a rejected large upload can still send the full body before the failure becomes visible.
$ curl --silent --show-error --verbose --http1.1 \ --header "Expect: 100-continue" \ --header "Content-Type: application/json" \ --data-binary @./release-bundle-2026.04.22.json \ --output /dev/null \ "https://uploads-api.example.net/v1/releases/release-bundle-2026.04.22/metadata" > POST /v1/releases/release-bundle-2026.04.22/metadata HTTP/1.1 > Host: uploads-api.example.net > User-Agent: curl/8.7.1 > Accept: */* > Expect: 100-continue > Content-Type: application/json > Content-Length: 74 < HTTP/1.1 100 Continue * upload completely sent off: 74 bytes < HTTP/1.1 201 Created
Setting the header explicitly applies the same approval step to small request bodies that cURL would otherwise send immediately.
Related: Send data in HTTP requests with cURL
$ curl --silent --show-error --verbose --http1.1 \ --expect100-timeout 0.2 \ --upload-file ./release-bundle-2026.04.22.tar.gz \ --output /dev/null \ "https://uploads-api.example.net/v1/releases/release-bundle-2026.04.22.tar.gz" > PUT /v1/releases/release-bundle-2026.04.22.tar.gz HTTP/1.1 > Host: uploads-api.example.net > User-Agent: curl/8.7.1 > Accept: */* > Content-Length: 1048704 > Expect: 100-continue * Done waiting for 100-continue * upload completely sent off: 1048704 bytes < HTTP/1.1 100 Continue < HTTP/1.1 201 Created
--expect100-timeout uses seconds, the default wait is one second, and cURL sends the body when that timer expires.