The IfModule trap on LiteSpeed: the homepage works, everything else 404s
We deployed the site to Hostinger. The homepage came up, static files were served, PHP ran. But /projects, /blog, /admin/login — all 404.
The wrong suspects
Three explanations came to mind first: .htaccess never reached the server, mod_rewrite is off, or the directory layout is wrong. All three were reasonable. All three were wrong.
There is one question that saves time in cases like this: who is returning the 404 — the web server or the application?
The headers answer it:
$ curl -sSI https://bulud.tech/admin/login
HTTP/2 404
last-modified: Tue, 22 Apr 2025 07:41:12 GMT
server: LiteSpeedThat last-modified is a year old. So this is not a page the application generated — it is LiteSpeed's own 404 file sitting on disk. PHP is never invoked.
For comparison, the application's own 404 carried content-type: text/html; charset=UTF-8, a session cookie and our security headers. A completely different response.
The decisive evidence
The real answer came from an unexpected place — the headers of an ordinary CSS file:
$ curl -sSI https://bulud.tech/assets/css/styles.css
HTTP/2 200
cache-control: public, max-age=604800cache-control is there. But x-frame-options and x-content-type-options are not — even though both were written in .htaccess.
That difference is the tell. The headers sat inside an <IfModule mod_headers.c> block. The routing rules sat inside <IfModule mod_rewrite.c>. Both blocks were being skipped.
The cause
LiteSpeed reads Apache's .htaccess syntax, but it does not evaluate <IfModule> the same way: module names are not registered in its internal table the way they are in Apache. The condition comes out false and the whole block is ignored — with no error and nothing in the log.
The homepage working was no coincidence: / resolves through DirectoryIndex straight to the index.php file on disk. It needs no rewrite. Every virtual URL does.
The fix
Take the critical directives out of the conditional:
DirectoryIndex index.php
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.php [L]The <IfModule> guard exists to avoid a 500 when a module is absent. But a site like this cannot work at all without mod_rewrite — so the guard protects nothing; it only makes the failure silent.
What we took away
Silent failure is the most expensive kind. An <IfModule> block says "skip quietly if the module is missing"; the problem is that "I don't recognise this module" and "this module is missing" are not the same thing.
The most useful diagnostic habit: establish who answered. A static file's headers show it in a single request — because no PHP is involved, everything you see is the server's own configuration.