The homepage works, but every post and page returns 404 Not Found. This almost always means the web server isn’t passing “pretty” URLs like /hello-world/ to WordPress’s index.php. It typically appears after moving to a new server, switching from Apache to Nginx, or changing permalink settings.

A URL like https://example.com/2026/11/hello-world/ doesn’t correspond to a file. The web server must hand any request for a non-existent file to index.php, which then looks up the post. If the server instead looks for a file or folder called hello-world, it finds nothing — 404.

Quick test: switch Settings → Permalinks to Plain (?p=123). If posts load, the rewrite configuration is the problem. Then fix it and switch back to “Post name”.

Fix for Nginx

Nginx ignores .htaccess entirely. Your server block needs this in location /:

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

Meaning: serve the file if it exists, else the directory, else pass the request to index.php with its query string. Check the active config:

sudo nginx -T | grep -n "try_files"

If it says try_files $uri $uri/ =404; (common in default configs), that’s the bug. Fix, then:

sudo nginx -t && sudo systemctl reload nginx

WordPress in a subdirectory (example.com/blog/):

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

See WordPress Nginx configuration.

Fix for Apache

Apache needs three things:

  1. mod_rewrite enabled:
sudo a2enmod rewrite
sudo systemctl restart apache2
  1. .htaccess allowed for the site directory, in the virtual host:
<Directory /var/www/example.com>
    AllowOverride All
</Directory>

Debian/Ubuntu’s default for /var/www is AllowOverride None, which silently ignores .htaccess. Reload Apache after the change.

  1. A correct .htaccess in the WordPress root:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

If the file is missing, visiting Settings → Permalinks and clicking Save regenerates it — provided WordPress can write to it (www-data needs write permission on the file, or create it yourself).

Flush the rewrite rules

WordPress stores its own rewrite rules in the database. After changing permalink structures, custom post types or certain plugins:

sudo -u www-data wp rewrite flush --hard

Or simply open Settings → Permalinks and click Save Changes without changing anything.

Only some URLs 404?

  • Custom post types (products, portfolios, events) — a plugin registered them after the rules were generated; flush rewrite rules.
  • Category or tag base conflicts — a page and a category with the same slug; rename one.
  • WooCommerce product pages — check WooCommerce’s permalink settings and flush.
  • After deleting a plugin — leftover rules or redirects; flush.
  • Multilingual plugins — language prefixes need flushing after configuration changes.
  • Old URLs from a previous structure — set up 301 redirects (Redirection plugin or Nginx rules) so links and search rankings carry over; see WordPress redirects.

Still 404 for everything?

  • Check that requests reach the right server block: curl -I -H "Host: example.com" http://127.0.0.1/hello-world/.
  • Look at the error log: sudo tail -f /var/log/nginx/error.log while loading a post — open() "/var/www/example.com/hello-world" failed confirms the missing try_files.
  • Caching: a page cache or CDN may be serving a cached 404 — purge it.
  • Security plugins or a WAF may block certain URL patterns.

After a migration

A complete checklist for moved sites lives in migrating WordPress to a VPS. The permalink problem is the most common post-migration issue when moving from Apache-based shared hosting to an Nginx VPS.

Related: WordPress redirects for moved or renamed URLs that now return 404.

  • WordPress
  • Nginx
  • Troubleshooting