Troubleshooting

Fix Nginx 404 errors with try_files for pretty URLs

Pretty URLs return 404 on Nginx when nothing routes them to your app. Add the right try_files line for WordPress, Laravel or a single-page app, and avoid root and alias mistakes.

BeginnerLinux server4 min read

Your home page loads, but every other page, such as /about/ or /blog/my-post, returns 404 Not Found. On Apache this was handled by an .htaccess file. Nginx ignores .htaccess, so it looks for a real file called /about/ on disk, does not find one, and gives up. The fix is a single try_files line that sends those requests to your app. This guide gives the right line for common apps and covers the other usual causes.

Before you start

  • A Linux VPS running Nginx.
  • SSH access with sudo.
  • The site's server block file, for example /etc/nginx/sites-available/example.com or /etc/nginx/conf.d/example.com.conf.

How try_files works

try_files checks a list of options in order and uses the first one that exists. The last item is the fallback:

Nginx
try_files $uri $uri/ /index.php?$args;

This reads: serve the file if it exists, else the folder, else pass the request to /index.php with the original query string. Your app's router then reads the original URL and shows the right page.

The default many configs ship with ends in =404, which is right for a plain static site but wrong for apps that route URLs themselves.

WordPress

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

Then, in WordPress, go to Settings → Permalinks and click Save Changes once. WordPress does not need to write a rules file on Nginx, but saving refreshes its own routing.

For WordPress in a subfolder such as /blog/:

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

More in Fix WordPress permalinks 404 on Nginx.

Laravel and most PHP frameworks

The web root must be the public folder, not the project folder:

Nginx
root /var/www/myapp/public;

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

Symfony, Slim, CodeIgniter 4 and similar frameworks use the same pattern with their own front controller.

Single-page apps (React, Vue, Angular)

A built single-page app has one index.html and handles routes in the browser. Every unknown path should get that file:

Nginx
root /var/www/app/dist;

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

If the app also calls an API under /api/, give the API its own location so missing API routes return real 404s instead of the HTML page:

Nginx
location /api/ {
    proxy_pass http://127.0.0.1:3000;
}

Static sites with clean URLs

For a static site where /about should serve about.html:

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

root vs alias mistakes

Inside a location, root adds the full URL path to the folder, and alias replaces the location prefix:

Nginx
location /static/ {
    root /var/www/app;
}

A request for /static/logo.png is served from /var/www/app/static/logo.png.

Nginx
location /static/ {
    alias /var/www/app/assets/;
}

The same request is served from /var/www/app/assets/logo.png.

Common mistakes:

  • Using root /var/www/app/static; in location /static/ makes Nginx look in /var/www/app/static/static/.
  • Missing trailing slash: with alias, if the location ends in /, the alias path must too.

The error log shows the exact path Nginx tried:

Bash
sudo tail -n 20 /var/log/nginx/error.log | grep "No such file"

Check which server block answered

A 404 on every page, including the home page, often means a different server block is answering, for example the default site. Check which server_name blocks exist for the port:

Bash
sudo nginx -T | grep -E "server_name|listen"

Then compare the error in your site's own log with the default log. If requests land in the default log, the server_name does not match the domain or the site is not enabled. See Fix Nginx showing the default page instead of your site.

Apply the change

Bash
sudo nginx -t && sudo systemctl reload nginx

Check it works

Request a pretty URL directly:

Bash
curl -I https://example.com/about/

You want 200. A real missing page should still return a 404 from your app, which you can see in the body:

Bash
curl -s https://example.com/this-does-not-exist | head -n 20

The app's own "page not found" design means routing works. A plain white Nginx 404 page means the request never reached the app.

Troubleshooting

  • Home page works, all other pages 404: try_files ends in =404 or is missing. Use the app's fallback line.
  • PHP files download instead of running after the change: the PHP location ~ \.php$ block is missing or broken. See PHP files downloading instead of running.
  • CSS and images 404 but pages work: a root or alias path is wrong in the static location. Check the path in the error log.
  • Laravel shows a 404 for every route: root points at the project folder, not public.
  • SPA works until you refresh a page: the fallback to /index.html is missing.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.