Skip to content
Control Plane Labs

100 Continue

Informational response defined by RFC 9110 §15.2.1.

Last updated July 27, 2026

Common causes at a glance

  • The client library added Expect: 100-continue automatically for a large request body
  • curl adds the header for PUT/POST bodies larger than 1 KB unless told not to
  • An HTTP/1.0 proxy or load balancer in the path does not understand the expectation
  • A server or WAF that answers 100 before running auth checks, defeating the point

Reproduce it

curl -v -X POST --data-binary @big.json -H 'Expect: 100-continue' https://api.example.com/v1/import

What 100 Continue means

100 exists so a client with a large body can find out whether the server will reject the request before spending bandwidth uploading it. The client sends headers only, with Expect: 100-continue, and waits. If the server likes what it sees it replies 100 Continue and the client streams the body. If not, it replies with a final status such as 401 or 413 and the client never sends the payload at all.

It is a genuinely useful optimisation that almost nobody configures deliberately, because HTTP libraries turn it on and off on your behalf.

What the spec says

RFC 9110 §15.2.1 defines the code, and §10.1.1 defines the Expect header field that triggers it. A server that understands the expectation must either send 100 or send a final error status. Importantly, a client that sends the expectation must not wait forever: the spec says it should send the body anyway after a short timeout, because an intermediary may have swallowed the expectation. Interim 1xx responses are forwarded by proxies but are invisible to most application code.

What actually causes it

Seeing 100 in a trace is normal, not an error. What you actually notice in production is the latency it adds when something in the path mishandles it. curl adds Expect: 100-continue by itself once a PUT or POST body exceeds roughly a kilobyte. If the peer never answers the expectation, curl waits — historically one second — and then sends the body anyway. A fleet of uploads each paying a fixed extra second is the classic symptom, and it usually points at an old proxy, an HTTP/1.0 hop, or a middlebox that strips the header on the way in but not the response on the way out.

How to debug it

Run the request under curl -v and look for the interim status line. You will see < HTTP/1.1 100 Continue followed later by the real status. If the 100 never appears and there is a visible stall before the body goes out, the expectation is being dropped somewhere.

The cheapest fix is to stop asking. Passing an empty header removes it:

curl -v -X POST --data-binary @big.json -H 'Expect:' https://api.example.com/v1/import

Python’s requests never sends the expectation, so if you are comparing a curl reproduction against application traffic that is one difference to rule out early. On the server side, nginx does not support sending 100 from a proxied upstream in every configuration, so check the proxy module docs before concluding the application is at fault.

Working examples

curl

# curl adds Expect: 100-continue on its own for bodies over ~1 KB.
# -v shows the interim response; -H 'Expect:' suppresses the whole dance.
curl -v -X POST \
  -H 'Content-Type: application/json' \
  --data-binary @big.json \
  https://api.example.com/v1/import

Python (requests)

import requests

# requests does not send Expect: 100-continue, so you have to opt in
# and read the interim response through the underlying urllib3 connection.
resp = requests.post(
    "https://api.example.com/v1/import",
    headers={"Expect": "100-continue", "Content-Type": "application/json"},
    data=open("big.json", "rb"),
    timeout=30,
)
print(resp.status_code)  # the FINAL status, never 100

Node (fetch)

// fetch() hides interim responses entirely. Use node:http to observe them.
import http from "node:http";

const req = http.request(
  { host: "api.example.com", path: "/v1/import", method: "POST",
    headers: { Expect: "100-continue", "Content-Type": "application/json" } },
  (res) => console.log("final:", res.statusCode),
);
req.on("continue", () => {
  console.log("got 100 Continue, sending body");
  req.end(JSON.stringify({ records: [] }));
});
req.on("response", (res) => console.log("server answered early:", res.statusCode));

Reference and tooling

The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.

Other 1xx codes