try_files is one of the most used Nginx directives — and one of the most misunderstood. It checks for files in order and serves the first one that exists; if none do, it falls back to the last argument. That one line powers pretty URLs in WordPress, Laravel’s front controller and single-page app routing.

Syntax

try_files file ... uri;
try_files file ... =code;

Every argument except the last is checked as a path relative to root (or alias). The last argument is the fallback: either an internal redirect to a URI, or =404-style status code.

  • $uri — the request path (/about) as a file.
  • $uri/ — the request path as a directory (then index applies).

Static site

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

/about.html → serve the file; /docs/ → serve /docs/index.html; anything else → 404.

Add clean URLs without .html:

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

Now /about serves about.html.

PHP front controllers (WordPress, Laravel, Symfony)

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

Real files (images, CSS) are served directly; everything else is internally redirected to index.php with the original query string. The application then routes /blog/hello-world/ itself. The index.php request goes through location matching again and reaches your \.php$ block.

Use ?$query_string or ?$args — they’re equivalent. Without them, query parameters are lost on the fallback.

Single-page applications (React, Vue, Angular)

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

Client-side routes like /dashboard/settings don’t exist as files, so the app shell is served and the JavaScript router takes over. See deploying a React app with Nginx.

Fallback to a named location (proxy)

Serve static files directly, send everything else to an app server:

location / {
    try_files $uri @app;
}

location @app {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
}

Named locations (@name) can only be reached internally — great for “files first, then the app” setups (Rails, Django with static files in the root, Node apps with a public folder).

Maintenance page

location / {
    try_files /maintenance.html $uri $uri/ /index.php?$args;
}

Create maintenance.html in the root to switch the whole site into maintenance mode; delete it to switch back. (For SEO, return a 503 instead during long maintenance.)

Pre-compressed or cached files

location / {
    try_files /cache$uri/index.html $uri $uri/ /index.php?$args;
}

Some static-cache plugins write HTML files to a cache directory; try_files serves them before falling back to PHP.

Common mistakes

Fallback that doesn’t exist — try_files $uri /index.php; with no PHP location configured causes Nginx to serve index.php as a download or an internal redirect loop (rewrite or internal redirection cycle).

Using =404 where a fallback is needed — try_files $uri $uri/ =404; on WordPress makes every permalink 404. See WordPress permalinks 404.

Combining with alias — try_files with alias has long-standing quirks; prefer root where possible, or test carefully.

Expecting the fallback to keep the method — internal redirects keep the request method and body; that’s fine for index.php, but a fallback to a static file with POST returns 405.

Security: PHP location with try_files — inside the PHP location, try_files $uri =404; ensures only existing .php files run (prevents path tricks). Debian/Ubuntu’s snippets/fastcgi-php.conf handles this. See Nginx with PHP-FPM.

try_files vs rewrite vs if

  • Use try_files for “does this file exist? otherwise…”.
  • Use return for redirects.
  • Use rewrite only when you must transform URLs with regex — see Nginx rewrite rules.
  • Avoid if (!-e $request_filename) patterns from old tutorials; try_files is the clean replacement.

Related: Nginx location blocks.

  • Nginx
  • Configuration
  • Web Server