If you generate a static site (or serve any site with clean URLs and no query strings), this single nginx line is the difference between "works" and "mysteriously 404s":

location / {
    try_files $uri $uri/ =404;
}

What it actually does

try_files tells nginx, in order:

  1. $uri — try the literal path (/blog/my-post).
  2. $uri/ — if that fails, try it as a directory (serves the directory's index.html).
  3. =404 — if both fail, return a real 404 instead of falling through to something weird.

This is why sites generated as public/blog/my-post/index.html work at /blog/my-post: nginx finds no file at /blog/my-post, then finds the directory and serves its index.html. Clean URLs, no redirects, no server-side rewrites.

The settings worth adding

location ~* \.(css|js|png|jpg|svg|ico)$ {
    expires 7d;
    add_header Cache-Control "public";
    access_log off;
}

gzip on;
gzip_types text/css application/javascript text/html application/json image/svg+xml;

The cache rule means your CSS/JS/images are served from the visitor's browser cache for a week — a big win for page speed. gzip shrinks the text assets on the fly.

The mistake I made

I once skipped the =404 and let the last fallback be index.html. Every unknown URL silently served the homepage — terrible for SEO and confusing for visitors. The =404 is not decoration; it is what makes broken links actually return 404.

Static sites are simple to serve, and try_files is the line that makes them feel finished. If you are deciding between a VPS and managed hosting for that static site, the calculator below compares the real numbers.

VPS / self-hosted$6.00
Managed cloud$18.65
Difference per month$12.65
NoteSample pricing — verify against current provider prices