How HTTP Works

Request header

> Expect: 100-continue

Expect Header: 100-continue, curl Delays and 417

Expect: 100-continue asks the server to approve a request before the body is sent. Why curl adds it past 1 MB, the 1s delay, -H "Expect:", and 417.

Reviewed 3 min readintermediate4 sourcesTry itMarkdown
Direction
Request
Category
Connection management
Spec
RFC 9110 §10.1.1
JS can set it
No: forbidden header name
On this page

TL;DR: Expect: 100-continue lets a client send only the headers, wait for 100 Continue (or a final error) from the server, and then send the body. curl adds it by itself for HTTP/1.1 bodies over 1 MiB or of unknown size and waits one second for the answer, which is the classic “uploads stall for exactly 1s” bug. -H 'Expect:' turns it off.

The exchange

PUT /videos/42 HTTP/1.1
Host: origin.example.com
Content-Type: video/mp4
Content-Length: 734003200
Expect: 100-continue

If the server accepts the headers (auth passed, size acceptable):

HTTP/1.1 100 Continue

and the client then sends the 700 MB body. If not, the server answers with a final status right away and the client never sends the body:

HTTP/1.1 401 Unauthorized
HTTP/1.1 413 Content Too Large

That is the whole point: failing fast before a large upload. RFC 9110 section 10.1.1 defines the field and section 15.2.1 the interim response. The client must not wait forever; it sends the body after a timeout, because a server may not support the mechanism.

The one-second delay in curl

From the libcurl source, EXPECT_100_THRESHOLD is 1024 * 1024, and the header is added when the request body is larger than that or its length is unknown. It applies only to HTTP/1.1. curl then waits for the interim response, one second by default:

curl -v -T bigfile.bin https://api.example.com/upload
> PUT /upload/bigfile.bin HTTP/1.1
> Host: api.example.com
> Content-Length: 5242880
> Expect: 100-continue
>
* Done waiting for 100-continue

If you see Done waiting for 100-continue followed by a roughly one-second gap, the hop in front of you did not send 100 Continue. Older curl releases used a smaller threshold, so a script that was fine on one machine can start stalling on another; check curl --version rather than assuming.

Fixes, in order of preference:

  1. Make the server side answer 100 Continue. Most current servers do, so a stall usually means a proxy, WAF or legacy appliance in between swallowing the header.
  2. Tell curl not to send it:
curl -H 'Expect:' -T bigfile.bin https://api.example.com/upload
  1. Keep the header but shorten the wait:
curl --expect100-timeout 0.2 -T bigfile.bin https://api.example.com/upload

Other clients: .NET’s HttpClient has ExpectContinue on the request headers, and Go’s http.Transport has ExpectContinueTimeout.

417 Expectation Failed

HTTP/1.1 417 Expectation Failed

A server or intermediary that does not implement the expectation answers with 417 (see 417). Because 100-continue is the only expectation in common use, the cause is nearly always an old HTTP/1.0 proxy or server that rejects the header instead of ignoring it. The fix is the same: send Expect: empty, and the request goes through without the handshake.

Server and proxy side

  • A server that sees Expect: 100-continue should send 100 Continue when it is willing to read the body, or a final status if it is not. In Go’s net/http server this happens automatically the first time the handler reads the request body, so a handler that rejects before reading never sends the 100 and the client just gets the final response.
  • An intermediary may answer the 100 itself on behalf of the origin. RFC 9110 permits that, which means a 100 does not prove the origin accepted the request, only that the proxy did.
  • A 100 is an interim response: the final status still follows after the body. See 100 Continue.
  • Size limits belong in the first response. Rejecting with 413 before the body arrives saves the upload; rejecting after is just wasted bandwidth.

Frequently asked questions

Why does my curl upload pause for one second?

curl sent Expect: 100-continue and is waiting for a 100 Continue interim response. By default it waits one second (--expect100-timeout), then sends the body anyway. A server or proxy that ignores the header causes exactly that pause. Disable the header with -H "Expect:" or raise nothing and fix the server.

When does curl send Expect: 100-continue automatically?

On HTTP/1.1 requests when the body is larger than 1 MiB, or when the size is unknown, for example chunked or streamed input. The cutoff is the EXPECT_100_THRESHOLD constant in libcurl source, currently 1024 * 1024 bytes. It is not sent for HTTP/2, and not for small bodies, so tiny POSTs never show the delay.

How do I stop curl sending Expect: 100-continue?

Pass an empty Expect header: curl -H "Expect:" ... removes the header entirely. In libcurl set CURLOPT_HTTPHEADER with "Expect:". You can also shorten the wait with --expect100-timeout 0.2 instead of removing it.

What does 417 Expectation Failed mean?

The server or an intermediary cannot meet the expectation in the Expect header. Today the only defined expectation is 100-continue, so a 417 usually comes from an old proxy or server that does not understand it. Remove the header and retry.

Do browsers send Expect: 100-continue?

MDN notes that none of the more common browsers send it, and it is a forbidden request header name for fetch and XMLHttpRequest, so page JavaScript cannot set it. It shows up from command-line tools and server-side HTTP clients.

Sources

  1. MDN Web Docs: Expectdeveloper.mozilla.org
  2. RFC 9110 section 10.1.1: Expectrfc-editor.org
  3. RFC 9110 section 15.2.1: 100 Continuerfc-editor.org
  4. curl: --expect100-timeoutcurl.se

Keep going

Browse /search