Back to Troubleshooting

Removing index.php from WordPress URLs | NOC.org Support

If your WordPress URLs look like example.com/index.php/2024/01/my-post/ instead of the clean example.com/my-post/, it means WordPress cannot use URL rewriting and is falling back to including index.php in every URL. This is usually caused by a missing or misconfigured Apache module, incorrect .htaccess permissions, or a server that does not support the required rewrite rules. This guide covers all the common causes and fixes.

Step 1: Check Permalink Settings

First, verify that WordPress is configured to use pretty permalinks:

  1. Go to Settings > Permalinks in the WordPress admin dashboard.
  2. Select any structure other than "Plain" (e.g., "Post name" is the most common choice).
  3. Click Save Changes.

When you save permalink settings, WordPress attempts to write rewrite rules to the .htaccess file. If it cannot write to the file, it will show a notice at the bottom of the page with the rules you need to add manually.

Step 2: Enable mod_rewrite (Apache)

WordPress pretty permalinks require Apache's mod_rewrite module. Check if it is enabled:

# Check if mod_rewrite is loaded:
apache2ctl -M | grep rewrite

# If no output, enable it:
sudo a2enmod rewrite
sudo systemctl restart apache2

# Verify it is now loaded:
apache2ctl -M | grep rewrite
# Output: rewrite_module (shared)

Step 3: Set AllowOverride to All

Even with mod_rewrite enabled, Apache must be configured to allow .htaccess files to override the server configuration. The AllowOverride directive controls this:

# Edit your VirtualHost or Apache config:
sudo nano /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

If AllowOverride is set to None (the default in many Apache configurations), the .htaccess file is completely ignored, and WordPress rewrite rules will not work.

# Test and reload:
sudo apache2ctl configtest
sudo systemctl reload apache2

Step 4: Verify the .htaccess File

WordPress generates the following rewrite rules in the .htaccess file in the WordPress root directory:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Verify the file exists and contains these rules:

# Check the .htaccess file:
cat /var/www/html/.htaccess

# If the file is empty or missing, WordPress could not write to it.
# Check the file permissions:
ls -la /var/www/html/.htaccess

# The file must be writable by the web server user:
sudo chown www-data:www-data /var/www/html/.htaccess
sudo chmod 644 /var/www/html/.htaccess

If the file does not exist, create it and paste the WordPress rewrite rules above, then save the permalink settings again in the WordPress admin.

Step 5: WordPress in a Subdirectory

If WordPress is installed in a subdirectory (e.g., /blog/), the RewriteBase must match:

# For WordPress at example.com/blog/:
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]

Nginx Configuration (try_files)

If your server runs Nginx instead of Apache, .htaccess files are not used at all. Nginx uses the try_files directive to achieve the same result:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php;

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

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

The key line is try_files $uri $uri/ /index.php?$args; — it tells Nginx to first look for an exact file match, then a directory match, and finally pass the request to index.php (WordPress's front controller). Without this line, all non-file URLs will return 404.

# Test and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx

Flushing Rewrite Rules

WordPress caches rewrite rules internally. If you have made server-level changes (enabled mod_rewrite, fixed AllowOverride) but URLs are still broken:

  1. Go to Settings > Permalinks.
  2. Without changing anything, click Save Changes.

This forces WordPress to regenerate and re-write the rewrite rules. You can also flush rewrite rules via WP-CLI:

wp rewrite flush

Permalinks Break After Migration

After migrating WordPress to a new server, permalinks often break because:

  • mod_rewrite is not enabled on the new server. Run sudo a2enmod rewrite.
  • AllowOverride is set to None in the new server's Apache config.
  • .htaccess was not copied during migration (hidden files are often skipped by default in cp or rsync without the right flags).
  • Different web server: Migrating from Apache to Nginx means .htaccess rules are irrelevant — you need to configure try_files in the Nginx server block.
  • File ownership changed: The .htaccess file is owned by a different user and Apache cannot read it.

After fixing the server configuration, always flush rewrite rules from the Permalinks settings page.

Troubleshooting Checklist

  1. Is mod_rewrite enabled? Check with apache2ctl -M | grep rewrite.
  2. Is AllowOverride All set for the DocumentRoot directory?
  3. Does .htaccess exist in the WordPress root with the correct rewrite rules?
  4. Is .htaccess readable by Apache? Check permissions and ownership.
  5. Did you save permalink settings after making server changes?
  6. If using Nginx, is try_files configured in the server block?
  7. Is WordPress in a subdirectory? If so, is RewriteBase set correctly?

For more on clean URLs and removing file extensions in general, see removing file extensions from URLs.

Improve Your Websites Speed and Security

14 days free trial. No credit card required.