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.

Steps to control Expect: 100-continue handling in cURL:

  1. Run a verbose HTTP/1.1 upload and confirm whether cURL adds Expect: 100-continue automatically.
    $ 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.

  2. Remove the handshake with an empty Expect: header when the upstream accepts immediate body transmission more reliably.
    $ 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.

  3. Force the provisional check on a small JSON request when the server should approve the request before reading even a short payload.
    $ 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.

  4. Lower the wait with --expect100-timeout when the server eventually returns 100 Continue but the default pause is too long.
    $ 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.