# 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.
Introduction
This article covers troubleshooting steps and solutions for Fix WordPress Permalink 404 Error. The error typically occurs in production environments and can cause service disruptions if not addressed promptly.
Symptoms
Common error messages include:
```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 ```
```bash # Flush rewrite rules wp rewrite flush --hard
# Verify .htaccess was regenerated cat .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 ```
Common Causes
- Configuration misconfiguration
- Missing or incorrect credentials
- Network connectivity issues
- Version compatibility problems
- Resource exhaustion or limits
- Permission or access denied
Step-by-Step Fix
```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.
Related Articles
- [WordPress troubleshooting: Fix Child Theme Not Enqueuing Parent Styles Correc](child-theme-not-enqueuing-parent-styles-correctly)
- [Fix Database Connection Error Custom Socket Path Issue in WordPress](database-connection-error-custom-socket-path)
- [Fix Debug Log Growing Deprecated Warnings Notices Issue in WordPress](debug-log-growing-deprecated-warnings-notices)
- [Fix Fix Contact Form Not Sending On Wordpress Site Issue in WordPress](fix-contact-form-not-sending-on-wordpress-site)
- [Fix Fix Open Basedir Restriction Blocking Wordpress Issue in WordPress](fix-open-basedir-restriction-blocking-wordpress)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Fix WordPress Permalink 404 Error", "description": "Resolve WordPress 404 errors on pretty permalinks. Fix .htaccess rewrite rules, Apache AllowOverride, Nginx configuration, and mod_rewrite issues.", "url": "https://www.fixwikihub.com/fix-wordpress-permalink-404", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2025-11-17T09:49:50.078Z", "dateModified": "2025-11-17T09:49:50.078Z" } </script>