Large HTTP uploads often fail before the body is worth sending, especially when an API, proxy, or object store rejects the request during authentication, quota, or policy checks. Controlling Expect: 100-continue in cURL keeps those failures predictable and reduces wasted bandwidth on retries and rejected uploads.

On HTTP/1.1 request paths, cURL can send Expect: 100-continue before the request body so the server can either return 100 Continue or reject the request early. cURL adds this automatically for PUT uploads and for POST bodies that are known or suspected to be larger than one megabyte, while –header “Expect:” disables it, –header “Expect: 100-continue” forces it, and –expect100-timeout changes how long cURL waits after sending the header.

The handshake only matters on HTTP/1.1, and some gateways or proxies delay it enough that the default wait becomes a visible stall. Keep verbose or trace output available while standardizing an upload pattern, because the correct setting depends on whether the upstream reliably answers with 100 Continue or performs better when the body is sent immediately.

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 before sending the body.
    $ curl --verbose --http1.1 \
      --upload-file ./release-bundle-2026.03.29.tar.gz \
      --output /dev/null \
      --write-out "HTTP %{http_code}\n" \
      "https://objects-api.example.net/v1/releases/release-bundle-2026.03.29.tar.gz"
    > PUT /v1/releases/release-bundle-2026.03.29.tar.gz HTTP/1.1
    > Host: objects-api.example.net
    > User-Agent: curl/8.x
    > Accept: */*
    > Content-Length: 1048704
    > Expect: 100-continue
    < HTTP/1.1 100 Continue
    < HTTP/1.1 201 Created
    HTTP 201

    On HTTP/1.1 PUT uploads, cURL adds the header automatically unless it is overridden.

  2. Remove the handshake with an empty Expect: header when the server or proxy handles immediate body transmission more reliably.
    $ curl --verbose --http1.1 \
      --header "Expect:" \
      --upload-file ./release-bundle-2026.03.29.tar.gz \
      --output /dev/null \
      --write-out "HTTP %{http_code}\n" \
      "https://objects-api.example.net/v1/releases/release-bundle-2026.03.29.tar.gz"
    > PUT /v1/releases/release-bundle-2026.03.29.tar.gz HTTP/1.1
    > Host: objects-api.example.net
    > User-Agent: curl/8.x
    > Accept: */*
    > Content-Length: 1048704
    * upload completely sent off: 1048704 bytes
    < HTTP/1.1 201 Created
    HTTP 201

    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 POST body when the upstream should approve the request before reading payload bytes.
    $ curl --verbose --http1.1 \
      --header "Expect: 100-continue" \
      --header "Content-Type: application/json" \
      --data-binary @./release-bundle-2026.03.29.json \
      --output /dev/null \
      --write-out "HTTP %{http_code}\n" \
      "https://objects-api.example.net/v1/releases/release-bundle-2026.03.29/metadata"
    > POST /v1/releases/release-bundle-2026.03.29/metadata HTTP/1.1
    > Host: objects-api.example.net
    > User-Agent: curl/8.x
    > Accept: */*
    > Expect: 100-continue
    > Content-Type: application/json
    > Content-Length: 86
    < HTTP/1.1 100 Continue
    < HTTP/1.1 201 Created
    HTTP 201

    Setting the header explicitly applies the same approval step to small request bodies that would normally be sent immediately.

  4. Shorten the wait window with –expect100-timeout when the server accepts the upload but the default pause adds avoidable latency.
    $ curl --verbose --http1.1 \
      --expect100-timeout 0.2 \
      --upload-file ./release-bundle-2026.03.29.tar.gz \
      --output /dev/null \
      --write-out "HTTP %{http_code}\n" \
      "https://objects-api.example.net/v1/releases/release-bundle-2026.03.29.tar.gz"
    > PUT /v1/releases/release-bundle-2026.03.29.tar.gz HTTP/1.1
    > Host: objects-api.example.net
    > User-Agent: curl/8.x
    > Accept: */*
    > Content-Length: 1048704
    > Expect: 100-continue
    * Done waiting for 100-continue
    < HTTP/1.1 100 Continue
    < HTTP/1.1 201 Created
    HTTP 201

    –expect100-timeout accepts decimal seconds, and cURL sends the body when the timer expires even if the provisional response arrives later.

  5. Capture a trace and verify the final policy before baking it into automation or CI jobs.
    $ curl --trace-ascii curl-expect-upload.trace --http1.1 \
      --upload-file ./release-bundle-2026.03.29.tar.gz \
      --output /dev/null \
      "https://objects-api.example.net/v1/releases/release-bundle-2026.03.29.tar.gz"
    $ grep -nE 'Expect: 100-continue|HTTP/1.1 100 Continue|HTTP/1.1 201 Created' curl-expect-upload.trace
    11:0079: Expect: 100-continue
    14:0000: HTTP/1.1 100 Continue
    16422:0000: HTTP/1.1 201 Created

    The trace is complete when it shows the request header policy, any provisional response, and the final application response in the same transfer.