# 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.Go to Settings > Permalinks
- 2.Don't change anything
- 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:
# 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 WordPressFix 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:
<Directory /var/www/html>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>Then restart Apache:
sudo systemctl restart apache2
# or
sudo systemctl restart httpdFix 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 Permalink Issues
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:
RewriteEngine On
RewriteLog "/var/log/apache2/rewrite.log"
RewriteLogLevel 5Then test a URL and check the log:
tail -f /var/log/apache2/rewrite.logCheck 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.[ ] .htaccess exists and has WordPress rules
- 2.[ ] mod_rewrite is enabled (Apache)
- 3.[ ] AllowOverride is set to All (Apache)
- 4.[ ] try_files is configured (Nginx)
- 5.[ ] File permissions allow WordPress to write .htaccess
- 6.[ ] Rewrite rules are flushed in database
- 7.[ ] No conflicting .htaccess rules
- 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.