Back to HTTP Headers

If-Modified-Since conditional request

Makes a request conditional on a timestamp — the older, date-based counterpart to ETag-based cache revalidation.

What it does

If-Modified-Since makes a request conditional on whether the resource has changed since a given timestamp. The client sends back the Last-Modified date it received on a previous response; the server compares that date against the resource's actual last-modified time and responds 304 Not Modified (no body) if nothing's changed since, or 200 OK with the current content if it has.

It's the original, time-based mechanism for conditional GET — predating ETag/If-None-Match, and still widely supported, but generally considered the less precise of the two validators since it can only detect change at whatever time granularity the server tracks (typically one-second resolution), and can't distinguish "genuinely different content" from "resource was re-saved with identical bytes."

Syntax

If-Modified-Since: <HTTP-date>

Always paired with a previously-received Last-Modified value, in HTTP's standard date format:

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

Only ever sent alongside GET or HEAD — it has no defined meaning on other methods.

How it drives caching

GET /report.pdf HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
Content-Type: application/pdf

<PDF content>

On the next request, the client sends that timestamp back:

GET /report.pdf HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

HTTP/1.1 304 Not Modified

If the file has since been updated:

GET /report.pdf HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

HTTP/1.1 200 OK
Last-Modified: Fri, 14 Aug 2026 10:15:00 GMT
Content-Type: application/pdf

<updated PDF content>

Why ETags are usually preferred

If-None-Match (ETag-based) has taken over as the primary conditional-GET mechanism in most modern setups, for a few concrete reasons:

  • Precision. An ETag can be a content hash, detecting any actual byte-level change. A timestamp only has whatever resolution the filesystem/server provides (commonly 1 second) — two different versions saved within the same second are indistinguishable to If-Modified-Since.
  • False negatives on identical re-saves. If a file gets rewritten with byte-for-byte identical content (a build process that touches every file regardless of actual changes, for example), its modification time updates even though nothing meaningfully changed — triggering an unnecessary full re-send that ETag-based validation would have correctly skipped.
  • Clock dependency. Timestamp comparison assumes reasonably synchronized clocks and consistent date handling across systems (including proxies/CDNs in the path), whereas ETag comparison is just string equality with no time-based assumptions at all.

When both If-None-Match and If-Modified-Since are present on the same request, If-None-Match takes precedence per spec — If-Modified-Since is effectively a fallback for resources/servers that don't support ETags.

Common mistakes and gotchas

Relying on it as the sole caching mechanism for frequently-regenerated content. Because of the one-second resolution limit and the "identical re-save still updates the timestamp" issue, If-Modified-Since alone can produce more unnecessary full responses than ETag-based validation would for content that changes (or is regenerated) frequently.

Manually formatting the date incorrectly. HTTP dates have a specific required format (Wed, 21 Oct 2015 07:28:00 GMT — always GMT, always that exact structure). A malformed date is typically just ignored by the server, silently disabling the conditional check rather than erroring visibly.

Not implementing it on the server despite sending Last-Modified. Similar to the ETag pitfall — sending Last-Modified on responses is only half the work; the server also needs to actually check If-Modified-Since on incoming requests and respond 304 when appropriate, or the header is just informational with no caching benefit.

Assuming sub-second changes are detected. If your resource can change multiple times within the same second (high-frequency updates, certain generated content), If-Modified-Since literally cannot distinguish those versions — this is a hard limitation of the format, not an implementation bug.

Real-world examples

Static asset revalidation:

GET /assets/logo.png HTTP/1.1
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT

HTTP/1.1 304 Not Modified
Last-Modified: Mon, 01 Jun 2026 12:00:00 GMT

Resource updated since last check:

GET /assets/logo.png HTTP/1.1
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT

HTTP/1.1 200 OK
Last-Modified: Thu, 13 Aug 2026 09:30:00 GMT
Content-Type: image/png

<updated image data>

FAQ

Should I use If-Modified-Since or If-None-Match for caching?

Prefer If-None-Match (ETag-based) when the server supports it — it's more precise and avoids the timestamp-resolution and identical-re-save issues If-Modified-Since has. If-Modified-Since remains a reasonable fallback for servers/resources that only track modification time and don't generate ETags.

What happens if both headers are sent on the same request?

If-None-Match takes precedence per spec — if it's present, the server should evaluate that condition and can ignore If-Modified-Since. This matters when a resource has both an ETag and a Last-Modified date but they'd give conflicting guidance (rare, but the precedence rule resolves it unambiguously).

Why did I get a full 200 response even though I sent a recent If-Modified-Since date?

Either the resource genuinely changed since that timestamp, the server doesn't implement conditional-GET checking for this endpoint at all (some dynamic/API responses skip it entirely), or there's a date formatting/parsing mismatch causing the server to treat the condition as invalid and fall through to a normal response.

Does If-Modified-Since work with HEAD requests?

Yes — conditional requests apply to both GET and HEAD. A HEAD with If-Modified-Since returns 304 (with no body, as HEAD never has one anyway) or 200 with headers reflecting the current resource state, same logic as GET just without transferring content either way.

Fun fact

If-Modified-Since/Last-Modified predates ETag in the HTTP specification — it was part of the original conditional-GET design going back to HTTP/1.0, when filesystem modification timestamps were the obvious, cheap validator to use (servers already tracked them for every file). ETag was introduced later specifically to address timestamp-based validation's precision limitations, but rather than replacing Last-Modified outright, HTTP kept both mechanisms — many servers still send both headers on every response, letting clients and intermediaries use whichever validator is available or more precise for their situation.