HTTP

Header

ETag

Learn how the ETag header provides a unique identifier for resource versions, enabling efficient cache validation and conditional requests to reduce bandwidth.

3 min read intermediate Try in Playground

TL;DR: Unique fingerprint for a resource version that enables efficient cache validation. Browsers send it with If-None-Match to get 304 Not Modified responses for unchanged content.

What is ETag?

ETag (Entity Tag) is a unique identifier for a specific version of a resource. It’s like a fingerprint for web content - when the content changes, the ETag changes too. Browsers use ETags to efficiently check if cached content is still fresh without downloading the entire resource again.

This enables smart caching that saves bandwidth and improves performance.

How It Works

1. Server sends resource with ETag:

HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json

{"message": "Hello World"}
```text

**2. Browser caches the response and ETag.**

**3. Later, browser asks "has it changed?":**

```http
GET /api/data HTTP/1.1
If-None-Match: "abc123"

4. If unchanged, server responds:

HTTP/1.1 304 Not Modified
ETag: "abc123"
```text

**5. Browser reuses cached version** - no data transfer needed!

## ETag Formats

### Strong ETags

Exact match required - any change creates new ETag:

```http
ETag: "abc123"

Used when byte-for-byte accuracy matters.

Weak ETags

Semantic equivalence - minor changes might keep same ETag:

ETag: W/"abc123"
```javascript

Used when content is semantically the same (e.g., gzip vs uncompressed).

## Generating ETags

### Content Hash

Most common approach - hash the content:

```javascript
const crypto = require('crypto')
const content = JSON.stringify(data)
const etag = crypto.createHash('md5').update(content).digest('hex')
// ETag: "a1b2c3d4e5f6"

Last Modified + Size

Combine modification time and file size:

const stats = fs.statSync(filename)
const etag = `${stats.mtime.getTime()}-${stats.size}`
// ETag: "1642781234567-1024"
```javascript

### Version Number

Use application version or database record version:

```javascript
const etag = `v${user.version}`
// ETag: "v42"

Database Row Version

Many databases provide automatic versioning:

-- PostgreSQL
SELECT content, xmin as version FROM articles WHERE id = 1;
```javascript

```javascript
const etag = `"${row.version}"`
// ETag: "12345"

Conditional Requests

If-None-Match (GET requests)

Check if resource has changed:

GET /api/users/123 HTTP/1.1
If-None-Match: "abc123"
```text

**Responses:**

- `304 Not Modified` - ETag matches, use cached version
- `200 OK` - ETag different, here's new content

### If-Match (PUT/DELETE requests)

Prevent lost updates:

```http
PUT /api/users/123 HTTP/1.1
If-Match: "abc123"
Content-Type: application/json

{"name": "Updated Name"}

Responses:

  • 200 OK - ETag matches, update successful
  • 412 Precondition Failed - ETag different, someone else modified it

Cache Validation Flow

First Request:

GET /api/data HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
ETag: "version-1"
Cache-Control: max-age=3600
Content-Type: application/json

{"users": [...]}
```text

**After Cache Expires:**

```http
GET /api/data HTTP/1.1
If-None-Match: "version-1"

HTTP/1.1 304 Not Modified
ETag: "version-1"
Cache-Control: max-age=3600

Browser reuses cached data for another hour.

Performance Benefits

Bandwidth Savings

Without ETags:
- Request: 500 bytes
- Response: 50KB (full content)
- Total: 50.5KB

With ETags (unchanged content):
- Request: 600 bytes (includes If-None-Match)
- Response: 200 bytes (304 Not Modified)
- Total: 800 bytes (98.4% savings!)

Faster Loading

  • No content transfer for unchanged resources
  • Instant cache revalidation
  • Better user experience

Real-World Examples

API Responses

GET /api/users HTTP/1.1

HTTP/1.1 200 OK
ETag: "users-v42"
Content-Type: application/json

[{"id": 1, "name": "John"}, ...]
```javascript

### Static Files

```http
GET /assets/app.js HTTP/1.1

HTTP/1.1 200 OK
ETag: "js-abc123"
Content-Type: text/javascript

function app() { ... }

Dynamic Content

GET /dashboard HTTP/1.1

HTTP/1.1 200 OK
ETag: "dashboard-user123-v5"
Content-Type: text/html

<html>...</html>
```text

## Best Practices

**1. Always include ETags for cacheable content:**

```http
ETag: "content-hash"
Cache-Control: max-age=3600

2. Use strong ETags for exact content:

ETag: "abc123"  # Strong ETag
```text

**3. Use weak ETags for equivalent content:**

```http
ETag: W/"abc123"  # Weak ETag for gzipped vs uncompressed

4. Handle conditional requests:

app.get('/api/data', (req, res) => {
  const etag = generateETag(data)

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end()
  }

  res.setHeader('ETag', etag)
  res.json(data)
})
```javascript

**5. Use ETags with optimistic locking:**

```javascript
app.put('/api/users/:id', (req, res) => {
  const currentETag = generateETag(currentUser)

  if (req.headers['if-match'] !== currentETag) {
    return res.status(412).json({ error: 'Resource was modified' })
  }

  // Safe to update
  updateUser(req.params.id, req.body)
})

Common Issues

Problem: ETags change on every server restart Solution: Use content-based ETags, not server instance IDs

Problem: Different ETags for same content across servers Solution: Use consistent ETag generation algorithm

Problem: ETags not working with load balancers Solution: Ensure all servers generate identical ETags

Frequently Asked Questions

What is an ETag header?

An ETag is a unique identifier for a specific version of a resource. Servers use it to detect changes and enable efficient cache validation without re-downloading unchanged content.

How do ETags work with caching?

The server sends an ETag with the response. On subsequent requests, the client sends If-None-Match with the ETag. If unchanged, the server returns 304 Not Modified.

What is the difference between strong and weak ETags?

Strong ETags (default) change when any byte changes. Weak ETags (prefixed with W/) change only for significant changes. Use weak ETags when minor changes like timestamps are acceptable.

Should I use ETag or Last-Modified?

Use both for maximum compatibility. ETag is more precise (byte-level), while Last-Modified has second-level granularity. ETags work better for dynamically generated content.

Keep Learning