
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
High-latency APIs and excessive egress costs often stem from redundant data transfer rather than slow backend logic. Implementing API caching with ETag and Last-Modified headers allows clients to validate cached responses without re-downloading identical payloads, solving this bottleneck at the protocol level. This guide covers the exact implementation patterns, validation logic, and common pitfalls for production-grade conditional requests.
How does API caching with ETag and Last-Modified actually work?
The mechanism relies on HTTP/1.1 validators defined in RFC 9110. When a server responds to a GET request, it includes an ETag (entity tag) or Last-Modified header representing that specific version of the resource. The client stores both the response body and these validators in its cache. On subsequent requests for the same URL, the client sends the validator back via If-None-Match (for ETags) or If-Modified-Since (for timestamps).
The server compares the incoming validator against the current resource state. If they match, the server returns a 304 Not Modified status with no message body. This saves bandwidth, reduces serialization overhead, and lowers latency significantly. For teams managing high-traffic endpoints, understanding this handshake is foundational to application-level caching strategies that complement network-layer optimizations.
A common mistake is treating ETags as simple cache keys. They are opaque validators. A weak ETag (W/"abc") indicates semantic equivalence but not byte-for-byte identity, suitable for dynamic content where minor formatting differences are acceptable. Strong ETags ("abc") guarantee bit-level equality and are required for range requests or resumable downloads. Choosing the wrong type breaks partial content delivery or causes false cache hits.
How do you implement ETag generation in backend applications?
Generating reliable ETags requires a deterministic function of the resource's state. Avoid hashing the entire response body on every request; this defeats the purpose by forcing full computation before validation. Instead, derive tags from metadata that changes only when the content changes.
Database-driven versioning
For CRUD resources, combine the primary key, updated_at timestamp, and a version counter. In PostgreSQL or MySQL, this looks like:
<?php
// Laravel example: Efficient ETag generation
$resource = Product::find($id);
$etag = hash('xxh3', implode('-', [
$resource->id,
$resource->updated_at->timestamp,
$resource->version
]));
return response()->json($resource)
->setEtag($etag)
->setLastModified($resource->updated_at); This approach ensures the ETag changes exactly when the underlying data mutates. The xxh3 hash is preferred over MD5 or SHA-256 for ETags because it is non-cryptographic and significantly faster, reducing CPU overhead during high-throughput validation checks.
Content-addressable storage
For static assets or immutable blobs, compute the hash once at upload time and store it as metadata. S3 objects, for instance, already provide an ETag based on the MD5 of the content (for non-multipart uploads). Re-computing this on every read is wasteful. Store the hash in your database or object metadata and serve it directly.
- Deterministic: Same input always produces the same ETag.
- Collision-resistant: Different states must produce different ETags.
- Efficient: Computation cost must be lower than serializing the full response.
- Opaque: Clients should never parse or depend on ETag structure.
When should you use Last-Modified instead of ETags?
Last-Modified provides second-level granularity. It is simpler to implement and debug because humans can read timestamps. However, it fails when multiple updates occur within the same second or when filesystem timestamps are unreliable (common in containerized environments or distributed storage).
Use Last-Modified as a fallback validator alongside ETags, not as a replacement. HTTP/1.1 specifies that if both If-None-Match and If-Modified-Since are present, the server MUST ignore If-Modified-Since unless the ETag comparison yields a match. This precedence prevents stale timestamp comparisons from overriding precise entity validation.
In practice, I recommend always emitting both headers when possible. This maximizes compatibility with older clients and intermediate proxies that may strip or mishandle one validator type. Monitoring tools like those discussed in metrics fundamentals should track 304 hit rates separately for ETag vs. Last-Modified to identify validation inefficiencies.
What are the common pitfalls in conditional request handling?
Misimplementing validators causes subtle bugs that are hard to diagnose in production. These are the most frequent issues I encounter during architecture reviews:
- Vary header omission: If your response differs based on Accept-Encoding, Authorization, or custom headers, you MUST include them in the
Varyresponse header. Without this, caches may serve a compressed response to a client that doesn't support gzip, or leak private data across users. The ETag alone does not account for representation variance. - Weak ETag misuse: Weak validators cannot be used for range requests. If your API supports partial content (206 Partial Content), you must use strong ETags. Returning a weak ETag with Accept-Ranges violates the spec and breaks download managers.
- Clock skew in Last-Modified: Distributed systems often have unsynchronized clocks. A file modified on Server A at 10:00:01 might appear older than a cached copy timestamped 10:00:00 on Server B due to NTP drift. Always use UTC and ensure NTP synchronization across all nodes generating timestamps.
- Ignoring If-Match for writes: Conditional PUT/PATCH requests use
If-Matchto prevent lost updates. Many APIs implement GET validation but skip write concurrency control. This leads to race conditions where two clients overwrite each other's changes silently. - Over-validation: Generating ETags by hashing the full JSON response on every request adds CPU cost that may exceed the savings from avoided transfers. Profile your validation logic. For large payloads, consider structural hashing (only hash changed fields) or version counters.
Security-sensitive endpoints require extra caution. Never include sensitive data in ETag computation if the tag itself could leak information. While ETags are typically hashes, predictable patterns can sometimes reveal resource existence or modification frequency. Review your threat model alongside your secrets management strategy to ensure validators don't become side channels.
How do ETag and Last-Modified compare for modern API architectures?
| Criteria | ETag | Last-Modified |
|---|---|---|
| Precision | Byte-level or semantic (strong/weak) | Second-level granularity |
| Computation Cost | Variable (hash function dependent) | Negligible (metadata lookup) |
| Distributed Systems | Safe (content-derived) | Risky (clock skew) |
| Range Requests | Required (strong only) | Insufficient alone |
| Human Debugging | Opaque string | Readable timestamp |
| Client Support | Universal (HTTP/1.1+) | Universal (legacy compatible) |
| Best For | Dynamic content, APIs, binaries | Static files, legacy systems |
Modern REST and GraphQL APIs should default to strong ETags derived from application state. Reserve Last-Modified for static asset pipelines or backward compatibility layers. The performance difference is negligible compared to the correctness guarantees ETags provide in distributed environments.
Implementing API Caching with ETag and Last-Modified in Production
Correct implementation of API caching with ETag and Last-Modified transforms API performance by shifting work from computation to comparison. Start by auditing your highest-traffic read endpoints. Add ETag generation based on resource versioning, emit Last-Modified as a secondary validator, and configure your reverse proxy to respect conditional requests. Monitor 304 ratios to validate effectiveness.
Remember that caching is a contract between client and server. Violating HTTP semantics breaks that contract and causes data inconsistency. Test thoroughly with real clients, not just curl scripts. Browser caches, mobile SDKs, and CDN edge nodes all behave differently under edge cases.
If you need help designing a caching strategy that aligns with your compliance requirements or infrastructure constraints, reach out to discuss your architecture. Proper validation caching pays for itself in reduced cloud bills and improved user experience within weeks of deployment.