HTTP

Method

HTTP HEAD Method

Learn how HTTP HEAD requests retrieve resource metadata (headers) without downloading the body. Useful for checking existence, size, and modification dates.

6 min read intermediate

What is HEAD?

TL;DR: HEAD gets resource metadata (headers) without downloading the actual content. Use it to check file sizes, validate links, or test if resources exist.

HEAD is like GET’s efficient twin—it retrieves all the same headers and metadata about a resource, but without the actual content body. Think of it like checking a book’s title page and table of contents without reading the entire book.

HEAD is perfect when you need to know about a resource (size, type, last modified date) but don’t need the actual data.

Key Characteristics

1. Safe

HEAD requests don’t change anything on the server. They’re read-only operations, just like GET.

2. Idempotent

Making the same HEAD request multiple times produces the same result with no side effects.

3. No Response Body

The server returns the same headers as a GET request would, but with an empty body. This makes HEAD requests much faster and uses less bandwidth.

4. Same Headers as GET

HEAD responses include all the same headers that a GET request to the same URL would return—Content-Type, Content-Length, Last-Modified, etc.

How HEAD Works

1. Client requests resource metadata:

HEAD /posts/42 HTTP/1.1
Host: api.example.com
Accept: application/json
```http

**2. Server responds with headers only:**

```http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1247
Last-Modified: Wed, 18 Jan 2026 10:30:00 GMT
ETag: "abc123def456"
Cache-Control: max-age=3600

[No body content]

Notice: Same headers as GET, but no actual post content in the response body.

Real-World Examples

Example 1: Checking File Size Before Download

HEAD /files/large-video.mp4 HTTP/1.1
Host: cdn.example.com
```http

**Response:**

```http
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 104857600
Last-Modified: Wed, 18 Jan 2026 09:15:00 GMT
Accept-Ranges: bytes

Now you know the file is 100MB before deciding to download it!

Example 2: Cache Validation

HEAD /api/posts/42 HTTP/1.1
Host: api.example.com
If-None-Match: "abc123"
```text

**Response (not modified):**

```http
HTTP/1.1 304 Not Modified
ETag: "abc123"
Cache-Control: max-age=3600

Example 3: Checking Resource Existence

HEAD /users/jane-doe HTTP/1.1
Host: api.example.com
```text

**Response (exists):**

```http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 342

Response (doesn’t exist):

HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 45
```text

### Example 4: Getting Image Metadata

```http
HEAD /images/profile-pic.jpg HTTP/1.1
Host: cdn.example.com

Response:

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 245760
Last-Modified: Tue, 17 Jan 2026 14:22:00 GMT
```text

### Example 5: Checking API Endpoint

```http
HEAD /api/v2/health HTTP/1.1
Host: service.example.com

Response:

HTTP/1.1 200 OK
Content-Type: application/json
X-API-Version: 2.1.0
X-Rate-Limit-Remaining: 999
```text

## When to Use HEAD

**✅ Use HEAD for:**

- Checking if a resource exists without downloading it
- Getting file size before downloading large files
- Cache validation (checking if content changed)
- Testing API endpoints and connectivity
- Getting metadata about images, videos, or documents
- Checking last modified dates
- Bandwidth-efficient resource monitoring

**❌ Don't use HEAD for:**

- When you actually need the resource content (use GET)
- Creating resources (use POST)
- Updating resources (use PUT or PATCH)
- Deleting resources (use DELETE)

## HEAD vs GET

| Feature             | HEAD          | GET                     |
| ------------------- | ------------- | ----------------------- |
| **Response body**   | No            | Yes                     |
| **Headers**         | Same as GET   | Full headers            |
| **Bandwidth usage** | Minimal       | Full content size       |
| **Speed**           | Fast          | Depends on content size |
| **Use case**        | Metadata only | Full resource           |
| **Safe**            | Yes           | Yes                     |
| **Idempotent**      | Yes           | Yes                     |

### Bandwidth Comparison

**GET request for 1MB image:**

```http
Request: ~200 bytes
Response: ~1MB + headers
Total: ~1MB
```text

**HEAD request for same image:**

```http
Request: ~200 bytes
Response: ~500 bytes (headers only)
Total: ~700 bytes
```http

HEAD uses 99.9% less bandwidth!

## Common Use Cases

### 1. Pre-flight File Checks

```javascript
// Check file size before downloading
async function downloadIfSmall(url, maxSize) {
  const headResponse = await fetch(url, { method: 'HEAD' })
  const fileSize = parseInt(headResponse.headers.get('content-length'))

  if (fileSize > maxSize) {
    throw new Error(`File too large: ${fileSize} bytes`)
  }

  // Now download the actual file
  return fetch(url)
}

2. Cache Validation

// Check if cached content is still valid
async function isCacheValid(url, cachedETag) {
  const headResponse = await fetch(url, { method: 'HEAD' })
  const currentETag = headResponse.headers.get('etag')

  return currentETag === cachedETag
}
```javascript

### 3. Resource Monitoring

```javascript
// Monitor API health without downloading data
async function checkEndpointHealth(url) {
  try {
    const response = await fetch(url, { method: 'HEAD' })
    return {
      status: response.status,
      healthy: response.ok,
      responseTime: Date.now() - startTime
    }
  } catch (error) {
    return { healthy: false, error: error.message }
  }
}
// Check if links are valid without downloading pages
async function validateLinks(urls) {
  const results = await Promise.all(
    urls.map(async (url) => {
      const response = await fetch(url, { method: 'HEAD' })
      return {
        url,
        valid: response.ok,
        status: response.status
      }
    })
  )
  return results
}
```text

## Common Response Codes

| Code    | Meaning            | When Used                                  |
| ------- | ------------------ | ------------------------------------------ |
| **200** | OK                 | Resource exists and is accessible          |
| **304** | Not Modified       | Resource hasn't changed (cache validation) |
| **400** | Bad Request        | Invalid request format                     |
| **401** | Unauthorized       | Authentication required                    |
| **403** | Forbidden          | Access denied                              |
| **404** | Not Found          | Resource doesn't exist                     |
| **405** | Method Not Allowed | HEAD not supported for this resource       |
| **500** | Server Error       | Server problem                             |

## Important Headers in HEAD Responses

### Content Headers

```http
Content-Type: application/json
Content-Length: 1247
Content-Encoding: gzip

Caching Headers

Last-Modified: Wed, 18 Jan 2026 10:30:00 GMT
ETag: "abc123def456"
Cache-Control: max-age=3600
Expires: Wed, 18 Jan 2026 11:30:00 GMT
```http

### Custom Headers

```http
X-API-Version: 2.1.0
X-Rate-Limit-Remaining: 999
X-Content-Source: cdn

Best Practices

1. Support HEAD for all GET endpoints

// Server should handle HEAD for any GET route
app.get('/posts/:id', (req, res) => {
  const post = findPost(req.params.id)
  if (!post) return res.status(404).json({ error: 'Not found' })

  res.json(post)
})

// HEAD automatically supported by most frameworks
// But you can handle it explicitly:
app.head('/posts/:id', (req, res) => {
  const post = findPost(req.params.id)
  if (!post) return res.status(404).end()

  res.set('Content-Type', 'application/json')
  res.set('Content-Length', JSON.stringify(post).length)
  res.end()
})
```javascript

**2. Use HEAD for efficient monitoring**

```javascript
// Monitor multiple services efficiently
const services = [
  'https://api.service1.com/health',
  'https://api.service2.com/health',
  'https://api.service3.com/health'
]

const healthChecks = await Promise.all(services.map((url) => fetch(url, { method: 'HEAD' })))

3. Implement proper caching headers

app.head('/files/:filename', (req, res) => {
  const file = findFile(req.params.filename)
  if (!file) return res.status(404).end()

  res.set({
    'Content-Type': file.mimeType,
    'Content-Length': file.size,
    'Last-Modified': file.modifiedAt.toUTCString(),
    ETag: file.etag,
    'Cache-Control': 'max-age=86400'
  })

  res.end()
})

Try It Yourself

Visit our request builder to experiment with HEAD:

  1. Select HEAD as the method
  2. Try path /posts/1 to get post metadata
  3. Compare with a GET request to the same URL
  4. Notice how HEAD is much faster and uses less data!
  • GET - Retrieve full resources
  • OPTIONS - Check allowed methods
  • POST - Create new resources
  • PUT - Replace resources

In Practice

Express.js
// Express handles HEAD automatically for GET routes.
// Define GET — HEAD is served for free.
app.get('/files/:id', async (req, res) => {
  const file = await storage.stat(req.params.id)
  if (!file) return res.status(404).end()
  res.set({
    'Content-Length': file.size,
    'Content-Type': file.mimeType,
    'Last-Modified': file.updatedAt.toUTCString(),
    'ETag': file.etag
  })
  // res.json() sends body for GET; HEAD strips it automatically
  res.json(file)
})
Next.js App Router
// app/api/files/[id]/route.ts
// Export HEAD handler explicitly
export async function HEAD(
  _req: Request,
  { params }: { params: { id: string } }
) {
  const file = await storage.stat(params.id)
  if (!file) return new Response(null, { status: 404 })
  return new Response(null, {
    headers: {
      'Content-Length': String(file.size),
      'Content-Type': file.mimeType,
      'ETag': file.etag,
      'Last-Modified': file.updatedAt.toUTCString()
    }
  })
}
Go net/http
// HEAD is handled automatically when you register a GET handler.
// To handle HEAD explicitly:
http.HandleFunc("/files/", func(w http.ResponseWriter, r *http.Request) {
    id := strings.TrimPrefix(r.URL.Path, "/files/")
    file, err := storage.Stat(id)
    if err != nil {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Length", strconv.FormatInt(file.Size, 10))
    w.Header().Set("Content-Type", file.MimeType)
    w.Header().Set("ETag", file.ETag)
    // For HEAD, WriteHeader without body
    if r.Method == http.MethodHead {
        w.WriteHeader(http.StatusOK)
        return
    }
    json.NewEncoder(w).Encode(file)
})

Frequently Asked Questions

What is the HTTP HEAD method?

HEAD is identical to GET but returns only headers, no body. Use it to check if a resource exists, get its size, or check modification time without downloading content.

When should I use HEAD instead of GET?

Use HEAD to check resource metadata without downloading: verify links exist, check Content-Length before download, validate cache with ETag/Last-Modified.

Does HEAD return the same headers as GET?

Yes, HEAD must return the same headers GET would return, including Content-Length and Content-Type. Only the body is omitted.

Is HEAD safe and idempotent?

Yes, HEAD is both safe (no side effects) and idempotent (same result on repeat). It should never modify server state.

Keep Learning