# 100 Continue

> The server received the request headers and the client should proceed to send the body. Learn when and how to use 100 Continue for efficient large uploads.

Source: https://howhttpworks.com/status-codes/100
Last reviewed: 2026-10-05

> **TL;DR:** `100 Continue` is the server saying "headers look fine, send the body." A client opts in by sending `Expect: 100-continue` and pausing before a large upload. It's an interim response with no body; the real result arrives in the final response that follows, which might be an error.

## When Does This Happen?

Say a client wants to upload a 2 GB file. It sends the request headers with `Expect: 100-continue` and pauses. If the server can already tell it will reject the request (wrong credentials, a size limit, an unsupported type), it sends that final error right away. Otherwise it sends 100 and reads the body. Either way, nobody wastes bandwidth on a body that was never going to be accepted.

Per [RFC 9110 §10.1.1](https://www.rfc-editor.org/rfc/rfc9110#section-10.1.1), the client doesn't have to wait forever; after a timeout it sends the body anyway. A server can also skip the 100 if some of the body has already arrived. And a 100 only means "keep going". Authentication, quota and content checks can fail once the server reads the body.

## Example Responses

Here's a trimmed HTTP/1.1 exchange with a five-byte body. The client sends its headers and waits:

```http
POST /upload HTTP/1.1
Host: localhost:8081
Content-Type: application/octet-stream
Content-Length: 5
Expect: 100-continue

```

The server answers with the interim response. The blank line ends it:

```http
HTTP/1.1 100 Continue

```

The client sends `hello`, and once the server has read it, it returns the final status:

```http
HTTP/1.1 204 No Content

```

Neither 100 nor 204 has a body. A real upload endpoint might just as well return an error here after validating what it received.

## Getting 100 Continue right

Node's HTTP server sends 100 automatically when it sees `Expect: 100-continue`, **unless you register a `checkContinue` listener**. By the time your normal request handler runs, the 100 has already gone out, so that's too late to reject on headers.

Once you handle `checkContinue`, Node skips the usual `request` event for those requests, so you have to route accepted ones to your body handler yourself. See the [Node HTTP documentation](https://nodejs.org/api/http.html#event-checkcontinue).

## Implementation Examples

Save this as `continue-demo.mjs` and run `node continue-demo.mjs`. It accepts one route and one media type, throws the body away, and returns 204. There's no storage or authentication; it's just for watching the exchange.

```javascript
import http from 'node:http';

function accept(req, res) {
  const status = req.method !== 'POST' || req.url !== '/upload'
    ? 404
    : req.headers['content-type'] !== 'application/octet-stream'
      ? 415
      : 0;
  if (!status) return true;
  res.writeHead(status, { Connection: 'close' });
  res.end();
  return false;
}

function consume(req, res) {
  req.on('end', () => {
    res.writeHead(204);
    res.end();
  });
  req.resume();
}

const server = http.createServer((req, res) => {
  if (accept(req, res)) consume(req, res);
});
server.on('checkContinue', (req, res) => {
  if (!accept(req, res)) return;
  res.writeContinue();
  consume(req, res);
});
server.listen(8081, '127.0.0.1');
```


On the client side, Node fires a [`continue` event](https://nodejs.org/api/http.html#event-continue) when the 100 arrives. Pair it with your own fallback timer. Save this as `continue-client.mjs` and run it while the demo server is up:

```javascript
import http from 'node:http';

const body = Buffer.from('hello');
let sent = false;
let final = false;
let timer;
const req = http.request('http://127.0.0.1:8081/upload', {
  method: 'POST',
  headers: {
    Expect: '100-continue',
    'Content-Type': 'application/octet-stream',
    'Content-Length': body.length,
  },
}, (res) => {
  final = true;
  clearTimeout(timer);
  console.log('final', res.statusCode);
  res.resume();
  res.on('end', () => {
    if (!sent) req.destroy();
  });
});
function sendBody() {
  if (sent || final) return;
  sent = true;
  clearTimeout(timer);
  req.end(body);
}
req.once('continue', sendBody);
req.on('error', (error) => {
  clearTimeout(timer);
  console.error(error.message);
});
req.once('close', () => clearTimeout(timer));
timer = setTimeout(sendBody, 1000);
req.flushHeaders();
```

[`flushHeaders()`](https://nodejs.org/api/http.html#requestflushheaders) pushes the headers out without the body. If you called `req.end(body)` right away instead, the body would go out before the server had a chance to decide. The one-second timer is this example's choice; Node doesn't impose one. The `sent` guard stops a late 100 from sending the body a second time after the timer already fired. Change the Content-Type to `text/plain` to see the server's immediate 415.

With a proxy in the middle, the proxy might send the 100 itself and the origin might reject the upload later. The 100 your client saw only tells you about that one connection. To debug, capture both legs or line up the proxy and origin logs, and judge success by the final status and what the origin actually stored.

## Try It Yourself

Use curl against that local server:

```bash
printf 'hello' > /tmp/continue-body.txt
curl --http1.1 -v --expect100-timeout 1 \
  -H 'Expect: 100-continue' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @/tmp/continue-body.txt \
  http://127.0.0.1:8081/upload
```

Run it again with `Content-Type: text/plain` and the server rejects the request before sending 100. curl's verbose trace shows both the interim and the final status.

curl's [`--expect100-timeout`](https://curl.se/docs/manpage.html#--expect100-timeout) defaults to one second. After that, it sends the body whether or not a 100 showed up. The option only sets the wait; the command above adds the `Expect` header explicitly.


To compare, send the same upload with the header suppressed:

```bash
curl --http1.1 -v -H 'Expect:' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @/tmp/continue-body.txt \
  http://127.0.0.1:8081/upload
```

This one goes through the server's ordinary request path. In the trace you'll see the body go out right after the headers, with no pause.

## Common Mistakes

HTTP doesn't define how long a client should wait for the 100. Each client picks its own timeout, so set it in the client you're using. If a server returns `417 Expectation Failed`, RFC 9110 says to retry without the expectation, which just sends the body straight away.

You can't test this from a browser. Fetch won't let you set `Expect`, because it's a [forbidden request header](https://fetch.spec.whatwg.org/#forbidden-request-header). Use curl or another HTTP client.

## 100 vs Other Informational Codes

`100` is about the request body: go ahead and send it. [103 Early Hints](https://howhttpworks.com/status-codes/103) is about the response: here are resources to start loading while the server finishes. Both are interim, and neither has a body.

## Related Status Codes

- [103 Early Hints](https://howhttpworks.com/status-codes/103)
- [417 Expectation Failed](https://howhttpworks.com/status-codes/417)
- [413 Content Too Large](https://howhttpworks.com/status-codes/413)
