GET says "send me this." HEAD says "describe this."
That's the entire difference. HEAD requests the same response as GET—same status code, same headers—but the server withholds the body. You learn everything about a resource without receiving it. Reconnaissance before commitment.
What HEAD Does
GET Request:
GET /large-video.mp4 HTTP/1.1
Host: example.com
→
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 524288000
Last-Modified: Wed, 15 May 2024 10:00:00 GMT
[524 MB of video data]
HEAD Request:
HEAD /large-video.mp4 HTTP/1.1
Host: example.com
→
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 524288000
Last-Modified: Wed, 15 May 2024 10:00:00 GMT
[No body]
You learn the file is 524 MB, it's an MP4, and when it was last modified—without downloading a single byte of video.
When to Use HEAD
HEAD shines when you need to know about resources without retrieving them:
Checking if Resources Exist:
HEAD /api/users/johndoe HTTP/1.1
→ 200 OK (user exists)
→ 404 Not Found (user doesn't exist)
Testing Links: Web crawlers and link checkers verify links without downloading pages:
HEAD /downloads/large-file.zip
→ Content-Length: 1073741824
// 1 GB—ask user before downloading
Verifying Resource Freshness:
HEAD /data/report.pdf
→ Last-Modified: Wed, 15 May 2024 10:00:00 GMT
→ ETag: "abc123"
// Compare with cached version
Determining Content Types:
HEAD /file-without-extension
→ Content-Type: application/pdf
// Now we know it's a PDF
HEAD and Caching
HEAD responses follow the same caching rules as GET. Browsers and proxies cache HEAD responses and can return them without contacting the origin server.
This makes HEAD useful for cache validation:
HEAD /api/data HTTP/1.1
If-Modified-Since: Wed, 15 May 2024 10:00:00 GMT
→
HTTP/1.1 304 Not Modified
// Cache is still fresh, no download needed
Implementation Requirements
RFC 91101 specifies that servers SHOULD send the same headers in response to HEAD as they would for GET. The specification uses "should" rather than "must" because some headers can only be determined while generating content—and generating full content just to discard it defeats HEAD's purpose.
This means a well-implemented HEAD handler:
Checks permissions
Determines content type
Calculates content length (if known without generating content)
Retrieve only metadata—don't generate full content just to discard it.
HEAD vs. GET for Existence Checks
HEAD Advantages:
Saves bandwidth (no body transferred)
Faster for large resources
Expresses intent clearly ("just checking")
GET Advantages:
You'll likely need the content anyway
Single request instead of HEAD then GET
Some servers don't support HEAD properly
General guideline: Use HEAD when you're only checking existence or metadata. Use GET when you'll need the content if it exists.
// Check if large file exists before downloadingconst headResponse = awaitfetch(url, { method: 'HEAD' });
if (headResponse.ok) {
const size = headResponse.headers.get('Content-Length');
if (confirm(`Download ${formatBytes(size)}?`)) {
const getResponse = awaitfetch(url);
// Download file
}
}
Security Considerations
HEAD requests can leak information. Apply the same protections as GET:
Authentication Required: HEAD should enforce the same authentication and authorization as GET:
HEAD /users/123/private-data
→ 401 Unauthorized (if not authenticated)
→ 403 Forbidden (if authenticated but not authorized)
Information Disclosure: Headers can reveal sensitive information:
HEAD /internal-document.pdf
→ Last-Modified: shows when document was updated
→ Content-Length: shows document size
→ X-Author: might reveal author's name
Ensure HEAD responses don't leak information that GET wouldn't also expose.
Server Support Inconsistencies
While servers should support HEAD for any GET endpoint, reality is messier:
Some servers return 405 Method Not Allowed
Some return different headers than GET would
Some frameworks don't automatically convert GET handlers to HEAD
Client workaround:
asyncfunctionheadRequest(url) {
try {
returnawaitfetch(url, { method: 'HEAD' });
} catch (error) {
// Fall back to GET if HEAD failsreturnawaitfetch(url);
}
}
HEAD and Range Requests
HEAD works with Range headers to check if a server supports partial content:
HEAD /large-file.zip HTTP/1.1
Range: bytes=0-0
→
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Length: 1
Content-Range: bytes 0-0/1073741824
// Server supports range requests, total size is 1 GB
Download managers use this to determine if parallel chunk downloads are possible.
HEAD in APIs
RESTful APIs should support HEAD for resource endpoints:
HEAD /api/v1/users/12345
→ 200 OK (user exists)
→ 404 Not Found (user doesn't exist)
HEAD /api/v1/articles/latest
→ Last-Modified: Wed, 15 May 2024 10:00:00 GMT
→ ETag: "abc123"
Some APIs use HEAD for health checks:
HEAD /health
→ 200 OK (service is healthy)
→ 503 Service Unavailable (service is down)
Performance Patterns
Conditional GET: Check freshness before downloading: