How to control Expect: 100-continue handling in cURL

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.