Nginx try_files Directive Explained

Last updated Sunday, May 17, 2026 1:02 pm Joshua James 9 min read

Nginx try_files decides what happens after a request matches a server or location block. It can serve a real file, check for a directory, send the request to an application entry point, jump to a named location, or return a status code such as 404. When it is wrong, the symptoms are familiar: static files disappear, single-page apps return blank pages, WordPress permalinks break, PHP requests show 404s, or the wrong backend receives the request.

A reliable try_files rule starts with the request type you want to protect: static assets should fail clearly when missing, application routes should reach the right entry point, and proxy fallbacks should stay explicit. Keeping those roles separate prevents a routing fix for one path from quietly breaking another.

Understand Nginx try_files Directive Behavior

The Nginx try_files directive checks candidate files in order. Each candidate is resolved against the current root or alias context. If one exists, Nginx serves it or continues processing it as the current request target. If none exists, the final argument decides the fallback.

PatternWhat Nginx DoesCommon Use
try_files $uri =404;Checks the exact requested file, then returns a 404 if it is missing.Static files, downloads, images, and protected asset locations.
try_files $uri $uri/ =404;Checks a file, then a directory, then returns a 404.Static sites where directories may contain an index file.
try_files $uri $uri/ /index.html;Checks a file and directory, then internally redirects to /index.html.Single-page applications that use browser-side routing.
try_files $uri $uri/ /index.php?$query_string;Checks static paths first, then passes unmatched routes to a PHP entry point.WordPress, Laravel, Symfony, and other PHP front-controller apps.
try_files $uri @backend;Checks a file, then jumps to a named location when the file is missing.Static-first reverse proxy setups and application fallbacks.

The last argument is special. If it starts with =, Nginx returns that status code. If it is a URI such as /index.html or /index.php?$query_string, Nginx performs an internal redirect. If it is a named location such as @backend, Nginx jumps to that internal location.

Do not use try_files as a general redirect tool. It is for internal file checks and internal fallback routing. Use Nginx redirect rules when the browser should receive a new URL.

Nginx try_files Context and Related Directives

try_files belongs in the server or location context. Most real configurations place it inside a location block because the correct fallback usually depends on the matched path.

Directive or VariableWhy It Matters
rootBuilds a filesystem path by appending the request URI to the configured document root.
aliasMaps a location prefix to a different filesystem directory. Path handling differs from root, so test aliased locations carefully.
$uriRepresents the normalized request URI path without the query string.
$uri/Checks whether the request maps to a directory.
$query_stringPasses the original query string to an application fallback such as /index.php.
@nameDefines an internal named location for fallback handling.

If the wrong location block handles the request, try_files may appear broken even when the directive itself is valid. Check regex, prefix, exact, and ^~ locations before changing the fallback; the Nginx 404 troubleshooting guide covers that routing check when the visible symptom is a missing page or asset.

Use Nginx try_files for Static Websites

For a plain static website, try_files should usually check the requested file, then the requested directory, then return a real 404. This keeps missing CSS, JavaScript, images, and downloads from falling through to an application route that hides the problem.

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.html;

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

In this configuration, a request for /assets/site.css checks /var/www/example.com/public/assets/site.css. A request for /docs/ checks the matching directory and can serve an index file through the normal index directive. If neither path exists, Nginx returns a 404 instead of silently routing the request somewhere else.

The $uri/ check is useful only when directory requests should be valid. If a matching directory exists but has no index file and directory listing is disabled, Nginx can return a directory-index 403. In that case, fix the index file, remove the directory check, or follow the Nginx 403 troubleshooting guide instead of treating it as a missing-file 404.

After editing the server block, test the configuration before reload:

sudo nginx -t

A successful syntax test prints these lines:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

On systemd-based Linux servers, reload Nginx only after the syntax test passes:

sudo systemctl reload nginx

Check a known file and a known missing path, replacing the hostname with the site you are testing:

curl -I http://example.com/
curl -I http://example.com/does-not-exist

Relevant headers should show a normal response for the real page and a 404 for the missing path:

HTTP/1.1 200 OK

HTTP/1.1 404 Not Found

Use Nginx try_files for Single-Page Applications

Single-page applications need a different fallback. The browser may request /dashboard, /settings/profile, or another route that does not exist as a physical file. In that case, Nginx should serve /index.html so the JavaScript router can handle the route.

server {
    listen 80;
    server_name app.example.com;

    root /var/www/app.example.com/dist;
    index index.html;

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

This pattern is correct for routes that belong to the SPA. It is not always correct for static assets. If a missing JavaScript file also falls back to /index.html, browsers can report confusing MIME type errors because they expected JavaScript but received HTML.

Use a stricter asset location when missing assets should return a true 404:

location ~* \.(?:css|js|mjs|map|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
    try_files $uri =404;
    expires 30d;
    add_header Cache-Control "public, immutable";
}

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

Run a header check against one SPA route and one deliberately missing asset:

curl -I http://app.example.com/dashboard
curl -I http://app.example.com/assets/missing.js

Relevant headers should show the app route served successfully and the missing asset rejected:

HTTP/1.1 200 OK

HTTP/1.1 404 Not Found

Use Nginx try_files with PHP-FPM Applications

PHP applications often use a front controller. Static files should be served directly, while unmatched routes should go to /index.php. That is where try_files helps.

server {
    listen 80;
    server_name php.example.com;

    root /var/www/php.example.com/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }
}

Replace the fastcgi_pass value with the socket or TCP address used by your PHP-FPM package. Debian and Ubuntu PHP packages often use versioned socket names, while some setups use 127.0.0.1:9000. A wrong PHP-FPM socket normally causes a 502, not a try_files problem.

The try_files $uri =404; line inside the PHP location is important. It prevents Nginx from handing arbitrary nonexistent .php paths to PHP-FPM. The SCRIPT_FILENAME line then tells PHP-FPM which file should be executed.

If the same site shows 404s for routes but 502s for PHP files, handle those symptoms separately. Use the Nginx 404 troubleshooting guide for file and route misses, and use the Nginx 502 troubleshooting guide for PHP-FPM socket, upstream, and service failures.

Use Nginx try_files with Named Locations

A named location is useful when you want Nginx to serve static files first and send everything else to an application backend. This avoids duplicating reverse proxy settings inside the main location block.

server {
    listen 80;
    server_name app.example.com;

    root /var/www/app.example.com/public;

    location / {
        try_files $uri $uri/ @app_backend;
    }

    location @app_backend {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Here, Nginx checks the public directory first. If the request does not map to a file or directory, it internally jumps to @app_backend. The browser never sees the named location. For the broader proxy configuration pattern, use the Nginx reverse proxy guide.

Verify both a static file and an application route after reload:

curl -I http://app.example.com/assets/site.css
curl -I http://app.example.com/api/health

If the static file works but the application route returns 502, the fallback reached the named location and the upstream failed. Check the backend service, port, and proxy settings rather than changing try_files first.

Use Nginx try_files with root and alias Carefully

try_files checks paths through the active filesystem mapping. With root, Nginx appends the request URI to the configured directory. With alias, Nginx maps the matched location prefix to a different directory. That distinction is one of the most common causes of unexpected 404s.

Use Nginx try_files with root

The root directive is the simplest case. A request for /images/logo.png under this server block checks /var/www/example.com/public/images/logo.png.

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com/public;

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

Use Nginx try_files with alias

Use alias when the URL prefix does not match the directory layout. In this example, requests under /downloads/ map to files under /srv/public-downloads/.

location /downloads/ {
    alias /srv/public-downloads/;
    try_files $uri $uri/ =404;
}

Keep the trailing slash pairing consistent: location /downloads/ should normally use alias /srv/public-downloads/;. If either side lacks the expected slash, Nginx can build paths you did not intend. Test aliased locations with exact URLs before applying the pattern to production traffic.

Avoid mixing aliased locations with broad application fallbacks unless you have tested the exact path behavior. A fallback such as /index.html is an internal URI, not an alias-relative filesystem path, so it may leave the aliased location and re-enter normal location matching.

Use Nginx try_files for Custom Error Pages

The fallback =404 returns a status code immediately. If you want a custom page, configure error_page separately so Nginx can return the right status while displaying your HTML file.

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com/public;
    index index.html;

    error_page 404 /404.html;

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

    location = /404.html {
        internal;
    }
}

The internal directive keeps /404.html available for Nginx error handling without turning it into a normal public route. If readers should be able to visit the error page directly, remove internal.

Test Nginx try_files Changes Safely

Use the same apply workflow after every try_files change: syntax test, reload, then request-level checks. A valid Nginx syntax test proves the configuration parses; it does not prove that the filesystem path, backend route, or application behavior is correct. The reload command below assumes a systemd-based Linux server.

sudo nginx -t && sudo systemctl reload nginx

Then test the exact paths that matter to the site:

curl -I http://example.com/
curl -I http://example.com/assets/site.css
curl -I http://example.com/missing-file
curl -I http://example.com/app-route

For a virtual host that is not live in DNS yet, send the host header directly to the local Nginx listener:

curl -I -H "Host: example.com" http://127.0.0.1/assets/site.css

That local check confirms how the configured server block handles the request before public DNS, CDN, or load balancer layers enter the path.

Troubleshoot Common Nginx try_files Errors

try_files problems usually come from one of four places: the request matched a different location, the filesystem path is wrong, the fallback points to the wrong internal URI, or the upstream/application layer failed after the fallback worked.

Nginx try_files Returns 404 for a File That Exists

First confirm which server block and filesystem mapping Nginx is using. The file may exist on disk but not under the root or alias path used by the matched virtual host.

sudo nginx -T 2>&1 | grep -E "server_name|listen|location|root|alias|try_files"

Relevant output should show the server name, listener, matching location, root or alias path, and the active try_files line:

listen 80;
server_name example.com;
location / {
root /var/www/example.com/public;
try_files $uri $uri/ =404;

Then check the actual file path:

ls -l /var/www/example.com/public/assets/site.css

If the file is not under the configured root or alias path, move the file, correct the filesystem mapping, or adjust the location that should serve it. If the path is correct but the status is still 404, check whether a more specific location is handling the request.

Nginx try_files Serves index.html for Missing Assets

This happens when an SPA fallback catches every missing path, including CSS, JavaScript, images, and font files. Add a stricter asset location before the app fallback:

location ~* \.(?:css|js|mjs|map|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
    try_files $uri =404;
}

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

After reload, a missing asset should return 404 Not Found, while a real client-side route should still receive index.html.

Nginx try_files Causes an Internal Redirection Cycle

If the fallback file is missing or the fallback URI re-enters the same failing logic, Nginx can log an internal redirect cycle. A common example is an SPA fallback to /index.html when index.html does not exist under the configured root.

Relevant error log fragments may include:

rewrite or internal redirection cycle while internally redirecting to "/index.html"

Check that the fallback file exists:

ls -l /var/www/app.example.com/dist/index.html

If the file is missing, rebuild or redeploy the application output. If the file exists, check whether the request is matching a different server block or a nested location that changes the active root.

Nginx try_files Does Not Fix PHP 502 Errors

try_files can decide whether a request should reach PHP-FPM, but it cannot repair a broken PHP-FPM socket, stopped service, or wrong upstream address. If static files work and PHP routes return 502, identify the PHP-FPM service name, then check that unit separately.

systemctl list-units --type=service --all 'php*-fpm.service' --no-pager
sudo systemctl is-active php-fpm
sudo journalctl -u php-fpm --since "30 minutes ago" --no-pager

Some distributions use versioned PHP-FPM unit names such as php8.3-fpm. If the unit list shows a versioned service, replace php-fpm in the status and journal commands with that service name. If the service is inactive or the socket path does not match fastcgi_pass, fix that layer before changing try_files.

Nginx try_files Loses Query Strings for PHP Routes

For PHP front-controller applications, include the original query string in the fallback:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

An equivalent pattern uses $is_args$args:

location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

Use one consistent style. Do not append a question mark blindly to every fallback, because static and SPA fallbacks usually do not need a PHP-style query-string handoff.

Use Nginx try_files Safely in Production

  • Keep root at the server level when possible so every location resolves files from the same predictable base path.
  • Use =404 for static asset locations where missing files should not fall through to an application.
  • Use named locations when a missing file should move to a reverse proxy or backend handler.
  • Use /index.html fallbacks for SPAs only after confirming real static assets still return real 404s when missing.
  • Use /index.php?$query_string or /index.php$is_args$args for PHP front-controller applications.
  • Run sudo nginx -t, reload Nginx, and verify with curl after each change.

If a try_files change fixes one URL but breaks another, do not keep stacking fallbacks in the same location. Recheck the matching order, split static assets from app routes, and use named locations when different request types need different handlers.

Conclusion

A clean Nginx try_files setup keeps each request type in its proper lane: static files return true 404 responses, SPA and PHP routes reach their entry points, and proxy fallbacks stay in named locations. Keep the same workflow for future changes: confirm the matching location and filesystem mapping, run nginx -t, reload, then verify the exact URL with curl before moving on to redirect, reverse proxy, or broader 404 troubleshooting.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: