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/eventsWhat 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
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 412 Precondition Failed
- 413 Content Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 422 Unprocessable Content
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request