WordPress pages return 404 on Nginx? Fix permalinks with one line
The home page loads, the admin works, but every post and page says "404 Not Found". This often happens after a move to a new server, or on a fresh Nginx setup. Apache reads WordPress's .htaccess file. Nginx ignores it, so you have to add one line yourself.
1. Whose 404 is it?
Ask for a post's headers with curl:
curl -sI https://example.com/hello-world/
- A plain white page with "nginx" at the bottom, and no
Link: <…/wp-json/>header: the request never reached WordPress. Nginx looked for a file calledhello-world, didn't find one, and stopped. Go to step 2. Nginx writes nothing to its error log for this. - Your theme's "page not found" page, with a header like this one: WordPress got the request but doesn't know the address. Go to step 4.
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=UTF-8
Link: <https://example.com/wp-json/>; rel="https://api.w.org/"
In our tests with the broken Nginx setup, the home page and /wp-admin/ worked, but every post, every page and the REST API (/wp-json/) gave Nginx's 404. That pattern is the sign.
2. Fix the try_files line
Open your site's file in /etc/nginx/sites-available/ and find the location / block. The Debian and Ubuntu default looks like this:
location / {
try_files $uri $uri/ =404;
}
The last part, =404, is the problem. Change it so that anything that isn't a real file or folder goes to WordPress:
location / {
try_files $uri $uri/ /index.php?$args;
}
In plain words: first look for a file, then a folder, and if neither exists, hand the address to index.php, along with anything after the ?. WordPress then reads the address and finds the right post.
You also need a working PHP block in the same server block. If PHP pages give "File not found." or 403, see fix 403 Forbidden and File not found first.
Don't drop ?$args. Without it, WordPress loses everything after the ?. In our test, /wp-json/wp/v2/posts?per_page=1 returned all 3 posts instead of 1, because the per_page=1 part never arrived. /index.php$is_args$args works the same as /index.php?$args; you'll see both in guides.
3. Test and reload Nginx
sudo nginx -t && sudo systemctl reload nginx
A reload keeps visitors connected. If nginx -t shows an error, nothing changes: fix the line it names first (see Nginx won't start or reload). Then check a post again:
curl -sI https://example.com/hello-world/ | head -n 1
HTTP/1.1 200 OK
In our test, all posts, pages and /wp-json/ gave 200 after the reload. An address that really doesn't exist still gave a 404, but now WordPress's own, with your theme.
4. WordPress's own 404: save the permalinks
If the 404 comes from WordPress (step 1), Nginx is fine. WordPress keeps a list of address patterns in the database, and that list can be out of date. This often happens after a plugin adds a new kind of content, such as products or events. In our test, a new "books" type gave WordPress's 404 for /books/my-book/ until we rebuilt the list.
To rebuild it, go to Settings → Permalinks in the dashboard and click Save Changes. You don't need to change anything. Or with wp-cli, as the site's user:
sudo -u shop wp rewrite flush --path=/var/www/shop.example/public
In our test, /books/my-book/ went from 404 to 200 right after that. On Nginx, saving permalinks does not fix an Nginx 404 from step 1: WordPress can't change your Nginx files.
5. WordPress in a subfolder
If WordPress lives in /blog/ and something else is at the top of the site, posts like /blog/hello-world/ get sent to the top folder's index.php, which isn't WordPress. In our test, /blog/ worked but /blog/hello-world/ gave 404. Add a block for the subfolder that points to its own index.php:
location /blog/ {
try_files $uri $uri/ /blog/index.php?$args;
}
Put it next to location /, in the same server block, then test and reload as in step 3. In our test, /blog/hello-world/ and /blog/wp-json/ then gave 200, and the main site still worked.
6. After moving to a new server
If your old server ran Apache, your permalinks worked because of .htaccess. Nginx doesn't read that file, so the new server needs the try_files line from step 2. Also check that the root line points to the folder where wp-config.php is. Our guide to moving a PHP site to a new server has a test step that catches this before you switch DNS.
If you can't log in to fix permalinks while you sort this out, /?p=1 style links still reach WordPress. They redirect to the pretty address, though, so they 404 again until Nginx is fixed.
Quick list
- Nginx's 404 page on every post:
try_files $uri $uri/ /index.php?$args;inlocation /. - Theme's 404 page: Settings → Permalinks → Save, or
wp rewrite flush. - Subfolder:
location /blog/ { try_files $uri $uri/ /blog/index.php?$args; }. - Keep
?$args, or search, filters and the REST API lose their settings. - Always:
sudo nginx -t && sudo systemctl reload nginx, then check withcurl -sI.
Official sources
- WordPress: Nginx (the
try_filesline and subfolder installs) - Nginx: try_files
- wp-cli: wp rewrite flush
Next steps: if WordPress loops between addresses instead, see too many redirects. If uploads or updates fail, check your file owners and permissions.
ServerNest is being built to write a tested Nginx setup for each WordPress site, so pretty links work from the start. Join early access to try it first.