# Fix WordPress Permalink 404 Error

You've set your permalinks to "Post name" or another pretty permalink structure, but clicking any link returns a 404 Not Found error. The homepage works fine, but every inner page is broken. This is almost always a rewrite rules or .htaccess issue.

Pretty permalinks rely on URL rewriting. Apache uses mod_rewrite via .htaccess, and Nginx uses try_files directives. When rewriting fails, WordPress never receives the request, and the server returns 404.

Quick Diagnosis

```bash # Check if permalinks are set correctly wp option get permalink_structure

# Test a post URL wp eval 'echo get_permalink(1) . "\n";'

# Check if .htaccess exists ls -la .htaccess

# Check Apache modules apache2ctl -M | grep rewrite ```

Fix 1: Regenerate .htaccess

The most common fix is regenerating the .htaccess file.

Via WordPress Admin

  1. 1.Go to Settings > Permalinks
  2. 2.Don't change anything
  3. 3.Click "Save Changes"

This regenerates .htaccess with correct rewrite rules.

Via WP-CLI

```bash # Flush rewrite rules wp rewrite flush --hard

# Verify .htaccess was regenerated cat .htaccess ```

Manually Create .htaccess

If WordPress can't write to .htaccess:

```bash # Create or overwrite .htaccess cat > .htaccess << 'EOF' # BEGIN WordPress 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] # END WordPress EOF

# Set correct permissions chmod 644 .htaccess chown www-data:www-data .htaccess ```

For Subdirectory Install

If WordPress is in a subdirectory:

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

Fix 2: Enable Apache mod_rewrite

If .htaccess is correct but permalinks still fail:

```bash # Check if mod_rewrite is enabled apache2ctl -M | grep rewrite

# Enable mod_rewrite (Debian/Ubuntu) sudo a2enmod rewrite sudo systemctl restart apache2

# Enable mod_rewrite (CentOS/RHEL) # mod_rewrite is enabled by default, but check: httpd -M | grep rewrite ```

Fix 3: AllowOverride Configuration

Apache needs to allow .htaccess overrides.

Check your Apache configuration:

```bash # Find Apache config files apache2ctl -S | grep "config file"

# Check virtual host configuration # Look for AllowOverride None and change to AllowOverride All ```

Edit your Apache site configuration:

apache
<Directory /var/www/html>
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

Then restart Apache:

bash
sudo systemctl restart apache2
# or
sudo systemctl restart httpd

Fix 4: Nginx Configuration

Nginx doesn't use .htaccess. You need proper try_files in your server block.

```nginx server { listen 80; server_name yourdomain.com; root /var/www/html; index index.php;

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

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

Test and reload:

```bash # Test Nginx config nginx -t

# Reload Nginx sudo systemctl reload nginx ```

Nginx Multisite Configuration

For WordPress Multisite with subdirectories:

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

# Handle uploaded files location ~ ^/files/(.+)$ { try_files /wp-content/blogs.dir/$blogid/$uri /wp-includes/ms-files.php?file=$1; } ```

For subdomain multisite:

```nginx server { server_name yourdomain.com *.yourdomain.com; root /var/www/html;

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

Fix 5: File Permissions

WordPress needs write access to create/update .htaccess:

```bash # Check current permissions ls -la .htaccess ls -la .

# Fix permissions chmod 644 .htaccess chown www-data:www-data .htaccess

# WordPress directory should be writable chmod 755 . chown www-data:www-data . ```

Fix 6: Database Rewrite Rules

Sometimes rewrite rules are corrupted in the database:

```bash # Check rewrite rules in database wp option get rewrite_rules

# Flush rewrite rules wp rewrite flush

# Or delete and regenerate wp option delete rewrite_rules wp rewrite flush ```

Fix 7: Check for Conflicting .htaccess

Multiple .htaccess files or conflicting rules cause issues:

```bash # Check for .htaccess in parent directories ls -la ../.htaccess

# Check for multiple .htaccess find . -name ".htaccess"

# Look for conflicting rules grep -r "RewriteRule|RewriteCond" .htaccess ```

Common conflicts:

  • Redirect rules before WordPress rules
  • Rules from security plugins that block requests
  • Hotlink protection rules

Fix 8: Custom Post Type 404s

If regular pages work but custom post types return 404:

```bash # Flush rewrite rules for custom post types wp rewrite flush

# Check if custom post type is registered wp eval ' $post_types = get_post_types(array("_builtin" => false), "names"); print_r($_post_types); '

# Check rewrite rules for CPT wp eval ' global $wp_rewrite; $rules = $wp_rewrite->rewrite_rules(); print_r(array_filter($rules, function($k) { return strpos($k, "your-cpt") !== false; }, ARRAY_FILTER_USE_KEY)); ' ```

The custom post type needs rewrite => true and flush_rewrite_rules on activation:

```php register_post_type('your-cpt', array( 'rewrite' => array('slug' => 'your-cpt'), // other args ));

// Flush on plugin activation register_activation_hook(__FILE__, 'flush_rewrite_rules'); ```

Debug Rewrite Rules

```bash # List all rewrite rules wp eval ' global $wp_rewrite; echo "Permalink structure: " . $wp_rewrite->permalink_structure . "\n"; echo "Rewrite rules:\n"; print_r($wp_rewrite->rewrite_rules()); '

# Match a URL against rules wp eval ' global $wp_rewrite; $url = "/your-post-slug/"; $match = $wp_rewrite->match($url); print_r($match); ' ```

Debug .htaccess Processing

Add to .htaccess for debugging:

apache
RewriteEngine On
RewriteLog "/var/log/apache2/rewrite.log"
RewriteLogLevel 5

Then test a URL and check the log:

bash
tail -f /var/log/apache2/rewrite.log

Check for URL Conflicts

```bash # Test if actual file/directory exists ls -la /var/www/html/your-post-slug/

# Check for conflicting files find . -name "*.html" -o -name "*.php" | grep -v wp- ```

Verification

After applying fixes:

```bash # Test homepage curl -I https://yourdomain.com/ # Should return 200 OK

# Test a post curl -I https://yourdomain.com/sample-post/ # Should return 200 OK, not 404

# Test a page curl -I https://yourdomain.com/sample-page/ # Should return 200 OK

# Test category archive curl -I https://yourdomain.com/category/uncategorized/ # Should return 200 OK ```

Common Scenarios

After Migration

If 404s appear after migrating to a new server:

```bash # Regenerate .htaccess wp rewrite flush --hard

# Check Apache modules apache2ctl -M

# Check file permissions ls -la .htaccess ```

After Changing Permalink Structure

```bash # Change structure wp rewrite structure '/%postname%/'

# Flush rules wp rewrite flush --hard ```

After SSL/HTTPS Change

```bash # Update site URLs wp option update siteurl 'https://yourdomain.com' wp option update home 'https://yourdomain.com'

# Regenerate .htaccess wp rewrite flush --hard ```

Quick Checklist

  1. 1.[ ] .htaccess exists and has WordPress rules
  2. 2.[ ] mod_rewrite is enabled (Apache)
  3. 3.[ ] AllowOverride is set to All (Apache)
  4. 4.[ ] try_files is configured (Nginx)
  5. 5.[ ] File permissions allow WordPress to write .htaccess
  6. 6.[ ] Rewrite rules are flushed in database
  7. 7.[ ] No conflicting .htaccess rules
  8. 8.[ ] Custom post types have rewrites registered

Permalink 404s are almost always configuration issues. Regenerate .htaccess, flush rewrite rules, and verify your server is set up for URL rewriting.