You migrate WordPress to a new server, switch your permalink structure to “Post name,” and suddenly every page except the homepage returns a 404. The fix sounds simple — but the root cause is almost never where you expect it.

Linux server terminal showing WordPress configuration and rewrite rules being edited via SSH command line
Fixing WordPress permalink issues typically requires SSH access and server-level configuration changes.

What’s Actually Happening

When WordPress is set to use Plain permalinks (e.g. /?p=123), every request goes directly to index.php with a query string. PHP handles it — no server magic required.

The moment you switch to a “pretty” structure like /sample-post/ or /category/post-name/, WordPress no longer appends a query string. The browser sends a request for a path (/about-us/) that doesn’t physically exist on disk. The web server needs to be told: “If this file or folder doesn’t exist, hand the request to WordPress’s index.php and let it figure it out.”

That instruction lives in rewrite rules. If the server doesn’t have them — or can’t read them — every non-homepage URL 404s. The homepage works because / maps directly to index.php, which always exists.

⚠ Why migration makes this worse

On a shared host, these rules are often pre-configured invisibly. When you migrate to a VPS or a different server stack, you’re starting from scratch — and the defaults rarely include WordPress rewrite support out of the box.

All the Reasons This Can Happen

  • 01
    Missing or malformed .htaccess The WordPress-generated rewrite block is absent, corrupted, or was overwritten during a plugin conflict or FTP transfer.
  • 02
    mod_rewrite not enabled (Apache) Apache’s rewrite engine is a loadable module. On a fresh Ubuntu/Debian install, it is disabled by default and must be explicitly activated.
  • 03
    AllowOverride not set to All (Apache) Even with mod_rewrite active, Apache won’t read your .htaccess unless the virtual host config has AllowOverride All. The default is often None.
  • 04
    Wrong document root or rewrite path (OpenLiteSpeed) OLS stores the .htaccess path in its vhost config. A typo, double slash, or stale migration path means OLS reads the rules from a file that doesn’t exist or is the wrong location.
  • 05
    try_files directive missing (Nginx) Nginx doesn’t use .htaccess at all. Rewrite logic must live inside the server block. Without the correct try_files directive, Nginx serves a 404 for every pretty URL.
  • 06
    Rewrite not enabled in OLS WebAdmin OpenLiteSpeed has a per-virtual-host toggle for rewrite engine and .htaccess auto-loading. Both can be independently disabled.
  • 07
    File permission or ownership issues If the web server user (nobody, www-data) can’t read .htaccess, it silently skips it — no error, just 404s.
  • 08
    Permalink settings not saved after migration WordPress writes its own .htaccess block when you click Save on the Permalinks page. If you restored a database but never visited that settings page, the block may be outdated or completely missing.
  • 09
    WordPress site URL mismatch If siteurl or home in the database still points to the old domain/IP, WordPress generates wrong internal redirect chains that break pretty URLs silently.
  • 10
    A caching or CDN layer serving stale 404s A misconfigured Cloudflare rule, server cache, or LiteSpeed Cache plugin can cache a 404 response and serve it indefinitely — even after the underlying rewrite rules are fixed.
Data center server rack representing web server infrastructure where Apache Nginx and OpenLiteSpeed run WordPress sites
Different web servers — Apache, Nginx, and OpenLiteSpeed — handle WordPress URL rewriting in fundamentally different ways.

Fix By Server Type

Find your web server below and follow only that section. You can confirm your server with:

BASH SSH → your server
# Check which web server is running
apache2 -v         # Apache
nginx -v           # Nginx
ls /usr/local/lsws # OpenLiteSpeed
A2
Apache2 — Ubuntu / Debian

Step 1 — Enable mod_rewrite

BASH
sudo a2enmod rewrite
sudo systemctl restart apache2

Step 2 — Set AllowOverride in your VHost config

Open your site’s Apache config (usually in /etc/apache2/sites-available/):

APACHE CONFIG /etc/apache2/sites-available/yoursite.conf
<VirtualHost *:80>
    ServerName yourdomain.com
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        Options Indexes FollowSymLinks
        AllowOverride All   # ← This is critical
        Require all granted
    </Directory>
</VirtualHost>

Step 3 — Verify .htaccess content

BASH
cat /var/www/html/.htaccess

It must contain the WordPress rewrite block. If it doesn’t, recreate it:

BASH /var/www/html/.htaccess
cat > /var/www/html/.htaccess << 'EOF'
# 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
EOF

sudo chown www-data:www-data /var/www/html/.htaccess
sudo chmod 644 /var/www/html/.htaccess
sudo systemctl restart apache2
NGX
Nginx
ℹ Note

Nginx ignores .htaccess completely. All rewrite logic must be placed directly in the Nginx server block config.

Step 1 — Edit your Nginx site config

NGINX CONFIG /etc/nginx/sites-available/yoursite.conf
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/html;
    index index.php index.html;

    # WordPress permalink rewrite — this is the critical block
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # Pass PHP to FastCGI
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    # Block .htaccess access (not needed but good practice)
    location ~ /\.ht {
        deny all;
    }
}

Step 2 — Test and reload

BASH
sudo nginx -t                   # Test config — must say OK
sudo systemctl reload nginx
⚠ Common mistake

Using try_files $uri $uri/ /index.php; without ?$args will break WordPress’s query strings (pagination, search, etc.). Always include ?$args.

OLS
OpenLiteSpeed

Step 1 — Check and fix the vhost config rewriteFile path

This is the most common OLS-specific cause. Open your vhost config:

BASH /usr/local/lsws/conf/vhosts/[yoursite]/vhconf.conf
# Find this block inside context / { ... rewrite { ... } }
# WRONG — double slash or wrong path:
rewriteFile    /var/www//.htaccess

# CORRECT — must exactly match your WordPress root:
rewriteFile    /var/www/html/.htaccess

Step 2 — Ensure rewrite is enabled in the same config

OLS CONFIG
context / {
  location              $VH_ROOT
  allowBrowse           1
  indexFiles            index.php

  rewrite  {
    enable              1
    inherit             1
    rewriteFile         /var/www/html/.htaccess
  }
}

rewrite  {
  enable              1
  autoLoadHtaccess    1
}

Step 3 — Fix file ownership and restart

BASH
# OLS runs as 'nobody' — confirm with: ps aux | grep lshttpd
chown nobody:nobody /var/www/html/.htaccess
chmod 644 /var/www/html/.htaccess

sudo /usr/local/lsws/bin/lswsctrl restart

Step 4 — Alternatively, use OLS WebAdmin Panel

Go to https://YOUR_IP:7080Virtual Hosts → [your vhost] → Rewrite and confirm:

  • Enable Rewrite = Yes
  • Auto Load from .htaccess = Yes

Save → Graceful Restart.

IIS
Windows IIS

Step 1 — Install URL Rewrite Module

IIS doesn’t ship with URL rewriting. Download and install the URL Rewrite 2.1 module from Microsoft’s official IIS downloads page, then restart IIS.

Step 2 — Create web.config in your WordPress root

XML C:\inetpub\wwwroot\web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="WordPress" patternSyntax="Wildcard">
          <match url="*" />
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="index.php" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Developer working on code editor screen showing WordPress PHP configuration fix for server permalink rewrite rules
After fixing server-level rewrite rules, always re-save WordPress permalink settings to regenerate the .htaccess block correctly.

The Final Step — Always Do This Last

Regardless of server type, after any rewrite config change, go to:

WP Admin → Settings → Permalinks → click Save Changes

This forces WordPress to regenerate the .htaccess rewrite block using the correct structure for your current permalink setting. No change needed — just open the page and save.

Quick Reference

ServerRewrite Config LocationKey Check
Apache2/etc/apache2/sites-available/*.confAllowOverride All + mod_rewrite enabled
Nginx/etc/nginx/sites-available/*.conftry_files $uri $uri/ /index.php?$args;
OpenLiteSpeed/usr/local/lsws/conf/vhosts/*/vhconf.confCorrect rewriteFile path, enable=1
IISweb.config in WordPress rootURL Rewrite Module installed

Bonus: Clear All Caches

After fixing the server config, if pages still 404, clear every cache layer in this order:

CHECKLIST
1. WP plugin cache (LiteSpeed Cache, W3 Total Cache, WP Rocket) → Purge All
2. Cloudflare → Caching → Purge Everything
3. Your browser → hard refresh (Ctrl+Shift+R / Cmd+Shift+R)
4. OLS / LiteSpeed server cache:
   sudo /usr/local/lsws/bin/lswsctrl restart
5. PHP OPcache:
   php -r "opcache_reset();"  # or restart PHP-FPM

Summary

WordPress pretty permalinks require the web server to redirect all non-existent paths to index.php. This works automatically on managed hosts because the stack is pre-configured. On a self-managed VPS or after a server migration, you have to set it up yourself. The exact mechanism varies by server — .htaccess + mod_rewrite for Apache, a try_files directive for Nginx, a correctly pointed rewriteFile for OpenLiteSpeed, and a web.config with URL Rewrite Module for IIS — but the underlying principle is identical across all of them.

Once the server-level config is in place, always finish by re-saving your permalink settings in the WordPress admin. That single click regenerates the correct rewrite block and flushes WordPress’s own internal rewrite cache.

Previous articleTop March 2026 PlayStation Store Downloads in Southeast Asia

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.