OpenLiteSpeed is said to support Apache's .htaccess syntax — but only part of it. Write an unsupported block and you get no error message; the page returns 403 and the working rules in the same file stop running too.
OpenLiteSpeed reliably processes only rewrite rules in .htaccess. <Files>, <FilesMatch>, <IfModule> and Header directives do not behave as they do on Apache; a file containing them produces a 403 on most installations, and the rewrite rules in that same file stop applying too. Security headers, cache lifetimes and file access restrictions belong in the virtual host configuration, not in .htaccess.
OpenLiteSpeed offers Apache compatibility as a compatibility layer; the engine itself is not Apache. In practice that means common rewrite rules work, while most other directives are either ignored or treated as a configuration error.
The problem is that this behaviour is silent. On Apache a bad configuration usually produces a 500 and a clear line in the error log. On OpenLiteSpeed you often see only a 403 — and the error log may carry nothing explaining why.
Worse: when the error is a block error, the entire file can be treated as invalid. The perfectly good HTTPS redirect at the top of the file stops working too. This is the most common cause of "I wrote a rule and nothing happens".
The rewrite family can be used safely:
# Force HTTPS and the non-www host in a single hop
RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
# index.html -> directory URL (kills duplicate URLs)
RewriteCond %{THE_REQUEST} \s/+(([^\s?]*/)?)index\.html[\s?] [NC]
RewriteRule ^ https://example.com/%1 [R=301,L]
RewriteEngine, RewriteCond, RewriteRule and their flags (R, L, NC, QSA) behave as expected. WordPress's own permalink rules fall in this category — which is why WordPress runs fine on OpenLiteSpeed.
These are routine on Apache and a source of trouble on OpenLiteSpeed:
# ✗ Security headers — do not work in .htaccess
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=31536000"
</IfModule>
# ✗ File access restriction — produces 403 and invalidates the file
<Files "wp-config.php">
Require all denied
</Files>
# ✗ Extension-based rules
<FilesMatch "\.(env|log|bak|sql)$">
Require all denied
</FilesMatch>
# ✗ Cache lifetimes
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/webp "access plus 1 year"
</IfModule>
The danger is not only that they do nothing. Added to a .htaccess file, they can take the working rules down with them. One day the site's HTTPS redirect quietly stops and nobody knows why.
If any of these three apply, go straight to the .htaccess contents:
Quick verification: rename the file temporarily and test the site.
mv .htaccess .htaccess.test
curl -sI https://example.com/ | head -1
# Did the 403 go away? The cause was inside the file.
mv .htaccess.test .htaccess
Then bisect the file to narrow down the offending block. Nearly every time it turns out to be an <IfModule>, <Files> or Header line.
Everything that does not fit in .htaccess goes into the virtual host configuration. On CyberPanel that is the site's vhost.conf, editable from the panel.
Security headers and cache lifetimes are defined there:
context / {
extraHeaders <<<END_extraHeaders
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security max-age=31536000; includeSubDomains
END_extraHeaders
}
expires {
enableExpires 1
expiresByType image/webp=A31536000, text/css=A31536000, application/javascript=A31536000, text/html=A300
}
Note the text/html=A300 detail: HTML should not be cached for long. On a static site, caching assets for a year while refreshing HTML every few minutes is the right balance — content updates appear quickly while CSS, JS and images are not re-downloaded.
For file access restrictions, use rewrites — these do work in .htaccess:
RewriteRule ^(.*/)?\.(env|git|htaccess|htpasswd)$ - [F,L]
RewriteRule \.(log|bak|sql|sql\.gz|tar\.gz|zip)$ - [F,L]
The [F] flag returns 403 and needs no block syntax, so it runs cleanly on OpenLiteSpeed.
One more detail, important for security: OpenLiteSpeed can serve static files without running them through rewrite rules. Good for performance, but with an unexpected consequence — your backup files may be unprotected.
The common scenario: you back a file up next to itself before editing.
# Risky habit
cp wp-config.php wp-config.php.bak
cp index.html index.html.20260902.bak
While those files sit in the web root, their addresses can be guessed and downloaded — and because .bak is not parsed as PHP, the contents appear as plain text. Including your database password.
The correct habit is keeping backups outside the web root:
mkdir -p /home/example.com/code-backups
cp /home/example.com/public_html/wp-config.php \
/home/example.com/code-backups/wp-config.php.20260902
The web server never sees that folder, so no access rule is needed.
On OpenLiteSpeed, treat .htaccess as a rewrite-only file. Headers, caching and file blocks belong in the vhost configuration; access denials are written as rewrite rules with the [F] flag.
And always test the site after a rule change: on this server errors do not make noise, they quietly stop working.
Partially. Rewrite rules (RewriteEngine, RewriteCond, RewriteRule) work reliably, which is why WordPress installs without trouble. But block directives such as Header, IfModule, Files and FilesMatch do not behave as on Apache and produce 403s on most installations.
Rename .htaccess temporarily and test. If the 403 clears, the cause is inside the file. Then bisect it; the culprit is almost always an IfModule, Files or Header block. Because such a block can invalidate the whole file, working redirects in it stop too.
In the virtual host configuration, under extraHeaders in the context block. On CyberPanel you can edit the site's vhost configuration from the panel. Header lines written into .htaccess do not work and may break the file.
Use a rewrite rule with the F flag instead of FilesMatch: RewriteRule \.(log|bak|sql)$ - [F,L]. It needs no block syntax and runs cleanly on OpenLiteSpeed. Even so, the safest route is keeping sensitive backups outside the web root entirely.