1. Library
  2. HTTP and the Web
  3. HTTP Methods

Updated 1 day ago

The HTTP HEAD Method

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:

async function checkLink(url) {
  const response = await fetch(url, { method: 'HEAD' });
  return response.ok;
}

Checking File Sizes Before Download:

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)
  • Sets cache headers and ETags
  • Does not send the body

Efficient implementation:

app.head('/articles/:id', (req, res) => {
  const metadata = getArticleMetadata(req.params.id);

  if (!metadata) {
    return res.status(404).end();
  }

  res.set('Content-Length', metadata.size);
  res.set('Content-Type', 'text/html');
  res.set('Last-Modified', metadata.modified);
  res.set('ETag', metadata.etag);
  res.status(200).end();
});

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 downloading
const headResponse = await fetch(url, { method: 'HEAD' });

if (headResponse.ok) {
  const size = headResponse.headers.get('Content-Length');

  if (confirm(`Download ${formatBytes(size)}?`)) {
    const getResponse = await fetch(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:

async function headRequest(url) {
  try {
    return await fetch(url, { method: 'HEAD' });
  } catch (error) {
    // Fall back to GET if HEAD fails
    return await fetch(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:

const headResponse = await fetch(url, { method: 'HEAD' });
const etag = headResponse.headers.get('ETag');

if (etag === cachedETag) {
  return cachedContent;
}

const getResponse = await fetch(url);

Preflight Size Checks:

const head = await fetch(downloadUrl, { method: 'HEAD' });
const size = parseInt(head.headers.get('Content-Length'));

if (size > MAX_SIZE) {
  throw new Error('File too large');
}

Availability Polling:

async function waitForResource(url) {
  while (true) {
    const response = await fetch(url, { method: 'HEAD' });

    if (response.ok) {
      return true;
    }

    if (response.status === 404) {
      await sleep(1000);
      continue;
    }

    throw new Error(`Unexpected status: ${response.status}`);
  }
}

Frequently Asked Questions About the HTTP HEAD Method

Sources

Sources

  1. RFC 9110 - HTTP Semantics

Was this page helpful?

😔
🤨
😃