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:
- Check exact path
$uri. - Check directory
$uri/. - 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.