Serving a static site is fast by default, but proper caching headers make a noticeable difference for repeat visitors. These are the headers I configure and why.

Cache-Control

This is the primary header that controls caching behavior. For static assets like CSS, JS, images, and fonts:

location ~* \.(css|js|png|jpg|jpeg|webp|svg|woff|woff2|ico)$ {
    add_header Cache-Control "public, max-age=2592000, immutable";
}

max-age=2592000 is 30 days. The immutable directive tells the browser not to revalidate the resource even on a normal reload. This works well when your build tool hashes filenames — if the content changes, the filename changes, so the old cached version is never requested again.

For HTML pages, shorter caching makes more sense:

location ~* \.html$ {
    add_header Cache-Control "public, max-age=3600";
}

One hour. This means content updates appear within an hour for returning visitors, while still reducing unnecessary requests.

ETag and Last-Modified

Nginx sends ETag and Last-Modified headers by default for static files. These enable conditional requests: the browser sends If-None-Match or If-Modified-Since, and the server responds with 304 Not Modified if nothing changed. No body is transferred.

This is useful for resources where you set a short max-age. After the cache expires, the browser revalidates instead of downloading the full resource again.

I leave Nginx’s default ETag behavior enabled. There is no reason to disable it unless you are running multiple backend servers that generate inconsistent ETags.

The Expires header

Expires is the older HTTP/1.0 way of setting cache duration. It takes an absolute date:

Expires: Thu, 15 Jan 2026 00:00:00 GMT

If you set Cache-Control: max-age, it takes precedence over Expires in modern browsers. I do not bother setting Expires separately since Nginx’s expires directive sets both:

expires 30d;

This adds both the Expires header and Cache-Control: max-age=2592000.

What I actually use

For this site, the configuration is straightforward. Hugo generates hashed asset filenames in production, so assets can be cached aggressively. HTML files get a shorter cache time. The relevant Nginx snippet is in my earlier post on Nginx configuration.

The key insight is that caching is not just about performance. It reduces server load, lowers bandwidth usage, and makes the site feel faster for returning visitors. For a static site on a small VPS, it is one of the simplest optimizations you can make.