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 (thenindexapplies).
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_filesfor “does this file exist? otherwise…”. - Use
returnfor redirects. - Use
rewriteonly when you must transform URLs with regex — see Nginx rewrite rules. - Avoid
if (!-e $request_filename)patterns from old tutorials;try_filesis the clean replacement.
Related: Nginx location blocks.