Updated:

Nginx try_files

try_files is one of the most important directives for a static site.
It tells Nginx: “check these paths in order and serve the first file that exists”.

For a static Astro build, a simple predictable setup is usually best.

Baseline

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

What it does:

  1. Check exact path $uri.
  2. Check directory $uri/.
  3. If nothing matches, return 404.

Why /en/docs/ works

With trailingSlash: 'always', Astro builds pages as directories with index.html:

dist/en/docs/index.html
dist/en/docs/cache-control/index.html

When the user opens:

/en/docs/cache-control/

Nginx checks the directory and serves:

/var/www/getsrv.app/en/docs/cache-control/index.html

What about /en/docs/cache-control

If the request has no trailing slash:

/en/docs/cache-control

try_files $uri $uri/ =404 first checks the file, then the directory.

If the directory exists, Nginx can serve index.html.

Controlled 404

A static site should have a controlled 404 page:

error_page 404 /404.html;

location = /404.html {
    internal;
}

Then a missing path:

curl -kI https://getsrv.app/no-such-page

returns:

HTTP/2 404
content-type: text/html

This is better than a random directory listing or a bare Nginx error page.

Assets should be separate

For /assets/, use a separate location:

location /assets/ {
    try_files $uri =404;
    access_log off;
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

There is no need for $uri/ here, because assets are concrete files, not page directories.

Bad fit for this site

location / {
    try_files $uri /index.html;
}

This is common for SPAs. For a static reference site, it is worse: missing pages may return the homepage with 200.

That breaks:

  • proper 404;
  • sitemap/SEO expectations;
  • diagnostics;
  • user expectations.

Check a concrete page

sudo test -f /var/www/getsrv.app/en/docs/cache-control/index.html && echo OK || echo missing
curl -kI https://getsrv.app/en/docs/cache-control/

Expected:

OK
HTTP/2 200

Check 404

curl -kI https://getsrv.app/no-such-page

Expected:

HTTP/2 404

Check assets

ASSET="$(find /var/www/getsrv.app/assets -maxdepth 1 -type f | head -n 1 | sed 's#^/var/www/getsrv.app##')"
curl -kI "https://getsrv.app$ASSET"

Expected:

HTTP/2 200
Cache-Control: public, max-age=31536000, immutable

Common mistakes

Mistake 1. SPA fallback instead of 404

try_files $uri /index.html;

Avoid it for a reference site.

Mistake 2. No error_page 404

Then different 404 cases may look inconsistent.

Mistake 3. Assets go through the common location

If /assets/ is not handled separately, assets may receive the HTML cache policy.

Mistake 4. Web root points to the wrong path

Check:

sudo grep -RIn "root " /etc/nginx/sites-enabled /etc/nginx/conf.d

Make sure root points to:

/var/www/getsrv.app

Minimal working set

root /var/www/getsrv.app;

location /assets/ {
    try_files $uri =404;
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location / {
    try_files $uri $uri/ =404;
    add_header Cache-Control "public, must-revalidate" always;
}

error_page 404 /404.html;

location = /404.html {
    internal;
}

For a normal static build, this is enough.