Request and response header
< Content-Length: 348Content-Length
Learn how Content-Length specifies the body size in bytes. Essential for progress indicators, connection management, and chunked transfer decisions.
- Direction
- Request and response
- Category
- Representation
- Spec
- RFC 9110 §8.6
- JS can set it
- No: forbidden header name
On this page
TL;DR:
Content-Lengthis the size of the message body in bytes, written as a plain decimal number. Count bytes, not characters, and count them after compression: for a gzipped body it’s the gzipped size. HTTP/1.1 chunked responses leave it out, and HTTP/2 and HTTP/3 don’t need it.
What is Content-Length?
The header gives the body size in bytes. In HTTP/1.1 it’s how the receiver knows where one message ends and the next begins. In HTTP/2 and HTTP/3 the framing handles that, but the header still tells the receiver how much to expect. RFC 9110 §8.6 defines it.
The value is digits only. Content-Length: 5 is valid; Content-Length: 5 bytes isn’t. No units, signs, or thousands separators.
How It Works
In this example response (trimmed, like the others on this page), Hello is exactly five ASCII bytes with no trailing newline:
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
Hello
The count covers the body only. Headers and chunk framing don’t count.
Why It Matters
With a known length, an HTTP/1.1 client knows when the response is done and can reuse the connection for the next request. Chunked encoding allows reuse too, so leaving out Content-Length doesn’t by itself force the connection to close.
For download progress bars, note that browser APIs can hand you decompressed bytes while the header counts compressed ones. Dividing one by the other doesn’t give you progress.
Calculating Content-Length
Café has four JavaScript string code units and five UTF-8 bytes:
const body = Buffer.from('Café', 'utf8')
console.log(body.length) // 5
If you gzip the body, measure the compressed buffer, not the original string. Node’s HTTP documentation also calls this out: use byte length, not character length.
When Content-Length is Required
No rule says every request with a body needs it. HTTP/1.1 requests can use chunked encoding, though a server is allowed to refuse those with 411 Length Required. If you know the length up front, send it instead of chunking; see RFC 9112 §6.3.
An HTTP/1.1 request with neither Transfer-Encoding nor Content-Length has an empty body. Unlike a response, a request can’t mark the end of its body by closing the connection.
When Content-Length is Not Used
Leave it off any message that uses Transfer-Encoding. It’s also not allowed on 1xx and 204 responses, or on a successful (2xx) response to CONNECT.
Two cases look odd but are fine. A HEAD response can carry the length a GET would have sent, even though it has no body. A 304 can carry the length of the full 200 response. In both, the number describes the resource; no body follows.
Content-Length vs Transfer-Encoding
With chunked encoding, each chunk carries its own size and a zero-length chunk marks the end. A response with neither header just ends when the server closes the connection. HTTP/2 and HTTP/3 use their own frames, but if you do send Content-Length, it has to match the bytes actually sent (except on responses that never have a body, like HEAD and 304).
Common Issues
A wrong length breaks more than progress bars. A body shorter than declared is incomplete. A declared length that’s too small leaves extra bytes that get parsed as another message. Treat any mismatch as a protocol error.
To check a real server, look at an actual GET, not just a HEAD:
curl --http1.1 -sS -D /tmp/length.headers -o /tmp/length.body \
-H 'Accept-Encoding: identity' https://example.com/
cat /tmp/length.headers
wc -c /tmp/length.body
Check Content-Encoding first. If the body came back compressed, compare the length with the compressed size.
Getting Content-Length right
For streamed bodies, let your HTTP library pick the framing. If middleware changes the body after the length was set, recalculate the length or drop it before the headers go out. A proxy that knows a length is wrong must not pass it along.
For JSON, serialize once and count the UTF-8 bytes of the result:
const body = JSON.stringify({ message: 'Café' })
const length = Buffer.byteLength(body, 'utf8')
Gzip that body afterwards and length is wrong. Either count the compressed buffer, or let your compression middleware drop the old header and pick the framing.
Server Examples
A complete Node server that counts the bytes it actually sends:
const http = require('node:http')
http.createServer((req, res) => {
const body = Buffer.from('Café', 'utf8')
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': body.length
})
res.end(body)
}).listen(3000)
To serve a file in Go, use http.ServeContent instead of setting the length yourself and copying bytes:
func report(w http.ResponseWriter, r *http.Request) {
f, err := os.Open("/srv/reports/report.pdf")
if err != nil {
http.Error(w, "Report unavailable", http.StatusNotFound)
return
}
defer f.Close()
info, err := f.Stat()
if err != nil {
http.Error(w, "Cannot stat report", http.StatusInternalServerError)
return
}
http.ServeContent(w, r, "report.pdf", info.ModTime(), f)
}
Drop this handler into a Go server that imports net/http and os. ServeContent seeks to find the size and handles range and conditional requests. A range response’s length is the size of the returned slice, so hard-coding the full file size would be wrong.
Related Headers
Frequently asked questions
What is Content-Length?
Content-Length specifies the size of the message body in bytes. It tells clients how much data to expect, enabling progress indicators and proper connection handling.
Is Content-Length required?
No. In HTTP/1.1 a body can use chunked encoding instead, and a response can end by closing the connection. HTTP/2 and HTTP/3 frame bodies themselves. Never send Content-Length together with Transfer-Encoding.
What happens if Content-Length is wrong?
If it is too large, the receiver waits for bytes that never come and sees an incomplete message. If it is too small, the leftover bytes get read as the start of the next message. Either way it is a protocol error, not a cosmetic bug.
How do I calculate Content-Length?
Count bytes, not string characters, and count them after any compression such as gzip. For streamed bodies, let your HTTP library handle framing.
Sources
Related
Content-Disposition Header
Learn how the Content-Disposition header controls whether content displays inline or downloads as an attachment. Set custom filenames for file downloads.
Content-Language Header
Learn how the Content-Language header specifies the natural language(s) of response content. Understand language tags and internationalization best practices.
Content-Location Header
Learn how Content-Location indicates an alternate URL for returned content. Useful for content negotiation and identifying canonical resource locations.
Date Header
Learn how the Date header specifies when the HTTP message was originated by the server. Understand date formats and its role in caching and logging.