Aditya Sharma

WordPress Permalinks 404 on nginx: Why Saving Does Nothing

On this page, 9 sections

WordPress pretty permalinks return 404 on nginx when the server has no try_files fallback to index.php. nginx ignores .htaccess, so saving the permalink settings cannot fix it. Add try_files $uri $uri/ /index.php?$args; to the location / block and reload nginx.

Every Apache-era guide says to go to Settings, Permalinks and click Save. On nginx that click rewrites nothing, and the core code shows why. This post covers the line you need, the way it fails, and how to tell an nginx 404 from a WordPress 404.

I read WordPress 7.1.3 source, the nginx try_files documentation and the WordPress nginx page on 11 October 2026. nginx is not installed on my machine, so I did not run nginx -t or any config below. Treat the config as taken from the documentation, not tested here.

The WordPress nginx documentation states it plainly: nginx has no directory-level configuration file like Apache’s .htaccess, so WordPress cannot modify the configuration. The rewrite has to live in the server config.

Core knows this. In wp-includes/vars.php, line 132 sets $is_nginx by checking whether SERVER_SOFTWARE contains “nginx”. On the Permalinks screen, wp-admin/options-permalink.php sets $writable to false when $is_nginx or $is_caddy is true. The screen never offers to write rules for you.

Underneath, save_mod_rewrite_rules() in wp-admin/includes/misc.php writes to .htaccess only if the file is writable and the server is Apache with mod_rewrite. A “hard” flush of rewrite rules, according to the comment in class-wp-rewrite.php, updates .htaccess on Apache or web.config on IIS. nginx is not on that list.

So the Save button regenerates the rules inside WordPress and then has nowhere to put them. If the 404s started after a migration from Apache, that is the whole story: the old host read .htaccess, the new one never will.

The line you need

The WordPress documentation gives this standard setup. The first block routes anything that is not a real file to index.php. The second passes PHP files to FPM and returns a 404 for a PHP file that does not exist.

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

location ~ \.php$ {
	try_files $uri =404;
	include fastcgi_params;
	fastcgi_pass php;
}

The fastcgi_pass php target is an upstream named in the docs’ example. Yours may be a socket path or a port. Do not copy that line blindly.

The nginx documentation describes try_files like this: it checks for files in the order given and uses the first one found. If none exist, it makes an internal redirect to the URI in the last parameter. A name ending in a slash checks for a directory.

Read the line as three steps. Is there a file at the request path? Is there a directory? If neither, hand the request to /index.php with the query string, and let WordPress decide what the URL means. ?$args keeps the original query string, so search and preview URLs still work.

Both blocks are needed, and the order of events explains why. A request for /sample-page/ matches location /. No file or directory exists, so try_files makes an internal redirect to /index.php?$args. That new URI is matched again, this time by the PHP block, and goes to FPM. Without the second block, nginx would send the PHP source to the browser or fail.

The version that causes the 404

The most common broken config ends in =404 instead of /index.php?$args:

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

The nginx documentation allows a status code as the last parameter. That is what =404 is. With it, a pretty permalink that matches no file ends the lookup with nginx’s own 404, and the request never reaches PHP. The home page still loads, because / resolves to a directory with an index file. Posts and pages fail.

That pattern, home page fine and every inner URL 404, is the signature. It is the first thing to check after a server move.

Tell an nginx 404 from a WordPress 404

A WordPress 404 renders your theme’s 404 template inside your site design. An nginx 404 is the server’s own error page, usually plain. Request the URL with curl and look at the headers and body.

curl -I https://example.com/sample-page/

Then request the same post by ID. This URL goes straight to index.php and does not need a rewrite:

curl -I "https://example.com/?p=123"

My reading of the source, not a test: if the ?p= URL loads and the pretty URL gives 404, the rewrite fallback is what is missing. If both fail, the problem is elsewhere, such as PHP not running, and WordPress too many redirects or How to fix 301 errors in WordPress will be closer to what you see.

Your nginx access log shows which status came back and which upstream answered. Reading WordPress access logs covers the fields.

Other places the rule can hide

  • More than one location block. A regex block such as one for static files can match before location /. Check each block that applies to the path.
  • Several config files. Hosting panels often include a separate file for custom rules. A line in the wrong file may never load. nginx -T prints the merged config.
  • WordPress in a subdirectory. The fallback has to point at that directory’s index.php.
  • A caching layer. After a fix, a cached 404 can keep appearing. Which caching layer is failing helps you find it.

After editing, run the config test and reload. I could not run it here, so this is the command from the nginx documentation, untested by me:

sudo nginx -t && sudo systemctl reload nginx

If an update left the site stuck rather than 404, that is a different problem. See WordPress stuck in maintenance mode.

How this was checked

I read vars.php, options-permalink.php, misc.php and class-wp-rewrite.php from the WordPress 7.1.3 tag on GitHub, the nginx try_files directive page and the WordPress nginx documentation, all on 11 October 2026. I did not run nginx, so nginx -t and every config block are unrun. I did not reproduce the 404. The ?p= diagnosis is my reading of how WordPress routes requests, not a measured result.

Questions people ask

Can I use a .htaccess file on nginx?

No. The WordPress documentation says nginx has no directory-level configuration file like .htaccess. Put rules in the server config.

Why does the home page work but posts 404?

The home page resolves to a directory. Posts need the fallback to index.php, and without it nginx ends the lookup.

Do I need to flush permalinks after changing nginx?

Not for the server fix. Reload nginx. Saving permalinks once afterwards does no harm, but it is not the fix.

Do this today

Run sudo nginx -T | grep -n "try_files" and read the last parameter on the line that covers location /. If it ends in =404, change it to /index.php?$args.

Resources

Tell me where I am wrong

Your email is not published and I do not add it to any list. Corrections with a source are the ones I act on fastest.