Finding a 500 Error

A 500 from One Bad Directive, and How to Find It

Apache 129 parses .htaccess per request, so configtest never sees it, and one bad line turns every URL below it into a 500. This file came from a server with mod_expires enabled:

An .htaccess copied from another serverApache config
# /var/www/example/.htaccess, copied from the old server
Header set X-Content-Type-Options "nosniff"
ExpiresActive On

The log names the file and the directive but no line. To get one, wrap the file in <Directory> and let configtest parse it, which catches syntax errors but not AllowOverride limits:

The symptom, the log entry, and a line numberShell
curl -s -o /dev/null -w '%{http_code}\n' localhost:8111/css/site.css
sudo tail -n 1 /var/log/apache2/example-error.log
printf '<Directory /var/www/example>\nInclude /var/www/example/.htaccess\n</Directory>\n' \
  > /tmp/htcheck.conf
sudo apache2ctl -t -c 'Include /tmp/htcheck.conf' 2>&1 | head -n 1
Output
500
[Wed Sep 23 15:58:00.605326 2026] [core:alert] [pid 121144:tid 121144] [client ::1:34432]
/var/www/example/.htaccess: Invalid command 'ExpiresActive', perhaps misspelled or defined by a
module not included in the server configuration
AH00526: Syntax error on line 3 of /var/www/example/.htaccess:

sudo a2enmod expires and a reload fix it; an <IfModule> wrapper would only hide the fault (Ignored Rules). The message also covers typos and a UTF-8 byte-order mark (Invalid command '\xef\xbb\xbfHeader'; sed -i '1s/^\xEF\xBB\xBF//' .htaccess strips it). All six below were reproduced here:

Error log messages from .htaccess faults
Log message (status) Usual cause See
Invalid command 'X' (500) Typo, BOM, module off 2.11.1
X not allowed here (500) Class not granted 2.4.5
bad flag delimiters (500) Space inside [...] 2.5.6
AH00124 (500) Internal rewrite loop 2.11.3
AH00529 (403) File unreadable 1.3
AH00670 (403) FollowSymLinks off 2.5