Skip to content
Control Plane Labs

411 Length Required

Client error response defined by RFC 9110 §15.5.12.

Last updated July 27, 2026

Common causes at a glance

  • A streamed or chunked request body sent to a server that requires Content-Length
  • A client library switching to Transfer-Encoding: chunked for a generator or file object
  • A POST or PUT with no body at all where the server still expects Content-Length: 0
  • An HTTP/1.0-era origin or CGI handler behind a proxy that cannot buffer streams

Reproduce it

curl -i -X POST -H 'Transfer-Encoding: chunked' --data-binary @body.json https://api.example.com/v1/events

What 411 Length Required means

HTTP/1.1 gives a client two ways to frame a request body: declare Content-Length in advance, or use Transfer-Encoding: chunked and let the framing carry the length. 411 is a server refusing the second option. It is not saying the body is malformed or too big, only that it declined to read a body whose size it cannot know in advance.

That refusal is usually deliberate. A size limit is far easier to enforce against a declared number than against bytes as they arrive, so servers that must run a WAF, a signature check, or a quota over the whole payload often demand a length.

What the spec says

RFC 9110 §15.5.12 is short: the server refuses the request without a defined Content-Length, and the client may retry with a valid one. The header itself is §8.6. The alternative framing, chunked transfer coding, is RFC 9112 §7.1, and the precedence rules for deciding a message body’s length are RFC 9112 §6.3. Chunked framing does not exist in HTTP/2 or HTTP/3, where DATA frames handle it, so a 411 over those versions points at a gateway translating down to HTTP/1.1 behind the scenes.

What actually causes it

The classic trigger is passing an iterator, generator, or unsized file object to an HTTP client. Both Python’s requests and Node’s fetch will silently choose chunked encoding when they cannot determine a length, and the upload dies against an origin that never supported it. IIS, some Java servlet configurations, and a fair number of API gateways refuse chunked request bodies outright. The second case is a POST with an empty body: some servers want an explicit Content-Length: 0 and treat its absence as a missing length rather than a zero-length body.

How to debug it

Establish which framing your client actually used before touching the server. Compare the two shapes directly:

curl -i -X POST -H 'Transfer-Encoding: chunked' --data-binary @body.json https://api.example.com/v1/events
curl -i -X POST --data-binary @body.json https://api.example.com/v1/events

curl sets Content-Length on its own for a file, so if the second command succeeds and the first returns 411 the diagnosis is settled. In application code the fix is to give the client something it can measure: read the payload into bytes, or set Content-Length yourself. Streaming a multi-gigabyte upload to a server that demands a length is not solvable client-side; you need resumable uploads or a presigned direct-to-storage URL. If the origin accepts chunked bodies but a hop in front does not, the header inspector shows which hop rewrote the framing.

Working examples

curl

# curl computes Content-Length from a file, so this normally succeeds.
curl -i -X POST \
  -H 'Content-Type: application/json' \
  --data-binary @body.json \
  https://api.example.com/v1/events

# Force chunked framing to reproduce the 411 on purpose:
curl -i -X POST -H 'Transfer-Encoding: chunked' \
  --data-binary @body.json \
  https://api.example.com/v1/events

Python (requests)

import requests

# A generator body makes requests fall back to Transfer-Encoding: chunked.
def stream():
    yield b'{"event":"click"}'

bad = requests.post("https://api.example.com/v1/events", data=stream(), timeout=30)
print(bad.status_code)  # 411 on a server that requires a length

# Materialise the body so requests can set Content-Length.
payload = b'{"event":"click"}'
ok = requests.post(
    "https://api.example.com/v1/events",
    data=payload,
    headers={"Content-Type": "application/json", "Content-Length": str(len(payload))},
    timeout=30,
)
print(ok.status_code)

Node (fetch)

// fetch() with a ReadableStream body uses chunked framing and trips 411.
const payload = JSON.stringify({ event: "click" });

const res = await fetch("https://api.example.com/v1/events", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    // A string body sets Content-Length automatically; be explicit when streaming.
    "Content-Length": String(new TextEncoder().encode(payload).length),
  },
  body: payload,
});
console.log(res.status);

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 4xx codes