- Home
- HTTP Headers
- ETag
Header
ETag
Learn how the ETag header provides a unique identifier for resource versions, enabling efficient cache validation and conditional requests to reduce bandwidth.
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 successful412 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
Related Headers
- If-None-Match - Conditional request header
- If-Match - Conditional request header for updates
- Last-Modified - Alternative cache validation method
- Cache-Control - Caching directives
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.