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

Updated 10 hours ago

GET says "give me this." HEAD says "tell me about 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.

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 are valid without downloading full 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

Checking Download Availability:

HEAD /exports/data-export-12345.csv
→ 200 OK (ready)
→ 202 Accepted (still processing)
→ 404 Not Found (doesn't exist)

HEAD and Caching

HEAD requests 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

Servers should support HEAD for any resource that supports GET. The HTTP specification requires HEAD to return the same headers that GET would return.

This means servers execute the same logic for HEAD as GET:

  • Check permissions
  • Determine content type
  • Calculate content length
  • Set cache headers
  • Generate ETags

The only difference: don't 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:

Directory Listing Prevention: If you block directory listing for GET, block it for HEAD too.

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.

Link Checking at Scale

Web crawlers and link checkers use HEAD to validate links efficiently:

async function checkLinks(urls) {
  const results = await Promise.all(
    urls.map(async url => {
      try {
        const response = await fetch(url, {
          method: 'HEAD',
          redirect: 'follow'
        });

        return {
          url,
          status: response.status,
          ok: response.ok
        };
      } catch (error) {
        return {
          url,
          status: null,
          ok: false,
          error: error.message
        };
      }
    })
  );

  return results;
}

This validates thousands of links far faster than GET requests would allow.

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
  • 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}`);
  }
}

Key Takeaways

  • HEAD returns headers without the body—reconnaissance before commitment
  • Use it to check existence, verify sizes, test links, and validate caches
  • Servers must return the same headers as GET; only the body differs
  • Implement HEAD handlers that retrieve metadata without generating full content
  • Apply the same security rules as GET—HEAD can leak information too

Frequently Asked Questions About the HTTP HEAD Method

Was this page helpful?

😔
🤨
😃