devkult_
tools29converters34
home/headers/expires

Expires

response headercaching

The HTTP/1.0 way to say when a response goes stale. Cache-Control: max-age overrides it.

Expires holds a date; until then, caches may reuse the response without asking. It predates Cache-Control and has two weaknesses: it depends on the client's clock being right, and it has to be recalculated for every response. When Cache-Control: max-age or s-maxage is present, caches ignore Expires.

Syntax

Expires: <http-date>

Defined in RFC 9111 §5.3 — Expires.

Examples

Expires: Thu, 15 Oct 2026 12:00:00 GMT

stale after this moment

Expires: 0

an invalid date, which caches treat as already expired

Good to know

  • Prefer Cache-Control: max-age; Expires only matters for very old HTTP/1.0 caches.
  • Any invalid value, including 0, means already expired.
  • nginx's expires directive sets both Expires and Cache-Control: max-age.

Further reading: HTTP caching explained

How to set Expires

nginx
location /static/ {
  expires 30d;  # sets Expires and Cache-Control: max-age=2592000
}
Apache (mod_expires)
ExpiresActive On
ExpiresByType image/png "access plus 1 month"
ExpiresByType text/css "access plus 1 week"
Express
// Cache-Control does the real work; Expires is for HTTP/1.0 caches
res.set("Cache-Control", "public, max-age=3600");
res.set("Expires", new Date(Date.now() + 3600 * 1000).toUTCString());

Check it

Fetch only the response headers and filter for Expires. In the browser, it's under Response Headers in DevTools → Network.

curl -sI https://example.com/ | grep -i '^expires:'

To compare response headers across many URLs at once, use the HTTP Status Checker.

Frequently asked questions

Do I need Expires if I send Cache-Control?

No. Every cache in use today understands Cache-Control, and max-age takes precedence over Expires when both are present.

Related headers