You save a small change to .htaccess, refresh the page, and the whole site shows 500 Internal Server Error. It looks serious, but it almost never is. Apache and LiteSpeed read .htaccess on every request, so a single bad line takes down every page at once β and the same single line is usually the whole fix.
This guide walks through the fastest way to confirm .htaccess is the cause, find the exact line that breaks it, and fix the six mistakes behind almost every case.
Step 1: Confirm .htaccess is really the cause (30 seconds)
Rename the file instead of deleting it. In your hosting file manager or FTP client, rename .htaccess to .htaccess-off, then reload the site.
- Site loads (maybe with broken pretty URLs): the problem is inside
.htaccess. Keep reading. - Still a 500 error: the cause is elsewhere β usually a PHP fatal error, wrong file permissions, or a memory limit. Check the PHP error log instead (our PHP Error Stack Analyzer reads it for you).
Rename the file back once you know. Without it, WordPress permalinks, HTTPS redirects and any security rules stop working.
Step 2: Read the error log β it names the line
A 500 error page hides the real message on purpose. The server's error log does not. On most hosts you'll find it in the control panel (hPanel, cPanel: Errors or Error Logs), or as an error_log file next to your site files. Look for lines like these:
/home/user/public_html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
/home/user/public_html/.htaccess: RewriteRule: bad flag delimiters
/home/user/public_html/.htaccess: <IfModule takes one argument, Container for directives based on existence of specified modules
/home/user/public_html/.htaccess: Option Indexes not allowed here
Each one maps straight to a fix below. If you can't find a log, use the bisect method in Step 4.
Step 3: Fix the six most common .htaccess mistakes
1. A typo in a directive name
Invalid command 'RewriteEgine' means exactly what it says. Directives are not forgiving about spelling. Compare carefully: RewriteEngine, RewriteCond, RewriteRule, ErrorDocument, Header.
2. A module that isn't installed or enabled
If the directive is spelled correctly but still "invalid", the module behind it isn't loaded β for example Header needs mod_headers and ExpiresActive needs mod_expires. Wrap optional blocks so they're skipped safely when the module is missing:
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
</IfModule>
Do not wrap your core rewrite rules this way. If mod_rewrite is missing, you want to know β otherwise your URLs quietly stop working.
3. Unclosed or mismatched blocks
Every <IfModule>, <Files> and <FilesMatch> needs a matching closing tag, and the opening tag needs exactly one argument. A common copy-paste accident:
<IfModule mod_rewrite.c β missing >
<IfModule mod_rewrite.c> β
...
</IfModule> β
every block closed
4. Bad RewriteRule flags
Flags go in square brackets, comma-separated, with no spaces: [L,R=301]. [L, R=301] or (L) triggers "bad flag delimiters". Also check for smart quotes pasted from a blog or Word document β β and β are not ".
5. Directives your host doesn't allow
not allowed here means the server's main config (AllowOverride) blocks that directive in .htaccess. Shared hosts commonly block Options and some php_value lines. Remove the line, or move PHP settings to a .user.ini file / your host's PHP settings page:
# Instead of this in .htaccess (breaks on PHP-FPM / LiteSpeed hosts):
php_value memory_limit 256M
# Put this in .user.ini:
memory_limit = 256M
6. Redirect loops that look like a 500
Two rules redirecting to each other (HTTPβHTTPS plus a plugin doing the same, or wwwβnon-www fighting a CDN setting) can end in "too many redirects" or an internal error after the server hits its rewrite limit. Keep exactly one place that handles each redirect. A safe HTTPS + non-www pair:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
If your site sits behind Cloudflare with "Flexible" SSL, %{HTTPS} is always off at your server, which causes an endless loop. Switch Cloudflare to "Full" or check X-Forwarded-Proto instead.
Step 4: No log? Find the bad line by halving the file
- Make a copy of the full file as a backup.
- Delete (or comment out with
#) the bottom half. Reload. - If the site works, the bad line is in the half you removed. If not, it's in the half you kept.
- Repeat on the guilty half until one line or one block is left.
Even a 200-line file takes about eight rounds. Comment out whole <IfModule> blocks together so you never leave one half-open.
Step 5: Rebuild a clean default (WordPress)
If the file is a mess of old plugin rules, start from the standard WordPress block and add back only what you need:
# 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
Then go to Settings β Permalinks and click Save once. WordPress rewrites its own section correctly.
Check your file before it goes live
The fastest way to avoid the next 500 is to check the file before you upload it. Paste it into the free .htaccess Validator. It flags unclosed blocks, bad flags, smart quotes and directives that commonly fail on shared hosting, so you fix them before visitors see an error.
Quick checklist
- Rename
.htaccessto confirm it's the cause. - Read the error log β it names the broken line.
- Check spelling, missing modules, unclosed blocks and flag syntax.
- Move
php_valuelines to.user.ini. - Keep one place for each redirect.
- Validate the file before every upload.