# LiteSpeed
Web server on kaburusvr.uk, managed via CyberPanel. Serves all client WordPress sites.
Version: OpenLiteSpeed 1.9.0-2
## Key paths
| Path | Purpose |
| —— | ——— |
| `/usr/local/lsws/` | LiteSpeed installation |
| `/usr/local/lsws/bin/lswsctrl` | Start / stop / restart / status |
| `/usr/local/lsws/conf/httpd_config.conf` | Main config |
| `/usr/local/lsws/conf/vhosts/` | Per-site vhost configs |
| `/usr/local/lsws/lsphp81/bin/lsphp` | PHP 8.1 binary |
| `/usr/local/lsws/lsphp83/bin/lsphp` | PHP 8.3 binary |
| `/usr/local/lsws/lsphp84/bin/lsphp` | PHP 8.4 binary |
| `/usr/local/lsws/lsphp85/bin/lsphp` | PHP 8.5 binary |
## Management
```bash # Graceful restart (use this — sends SIGUSR1, no dropped connections) /usr/local/lsws/bin/lswsctrl restart
# Status /usr/local/lsws/bin/lswsctrl status ```
—
## ModSecurity WAF (OWASP CRS)
WAF runs on kaburusvr since 2026-08-15 (OWASP CRS v4.28). Blocks are triaged by `/usr/local/bin/modsec-classify.py` (classify, don't count).
### Custom exclusions - File: `/usr/local/lsws/conf/owasp/modsec_custom_rules.conf` (survives the Sunday 04:30 CRS auto-update — never put custom rules in `rules/`) - Wired into: `/usr/local/lsws/conf/owasp/modsec_includes.conf` (included after CRS rules) - Known FPs whitelisted: 944120 (FastPixel cache writeback), 932235 on `_wp_original_http_referer`/`referredby`, 941180 on `content` (Gutenberg `<!–` comments)
### Classifier - Script: `/usr/local/bin/modsec-classify.py [hours]` — parses `/usr/local/lsws/logs/error.log`, groups by (rule, field, uri), buckets into SUPPRESSED (known FP) / ATTACK (LFI/RFI/RCE/SQLi/PHP-inj/scanner + secret-file probes) / REVIEW (new signatures). - Wired into: `/home/kaburu/.hermes/scripts/snmp-trap-check.sh` — emits `🛡️ ModSecurity WAF (24h): N attack, M new` only when N or M > 0. - No CyberPanel GUI exists — CyberPanel ships only an install toggle + raw audit-log viewer (textarea). Triage is classifier-driven.
### New-block workflow When REVIEW shows a new signature: pull the block from `error.log`, decide FP-vs-attack, update the classifier's WHITELIST/ATTACK lists, re-deploy via scp (base64 corrupts — use scp), re-run.
—
## PHP configuration — per-vhost overrides
PHP settings are applied per-site via the `phpIniOverride` block in each vhost config, not by editing php.ini globally. This keeps changes scoped to the affected site and survives CyberPanel/LiteSpeed updates.
### Standard baseline (applied to all sites — 2026-05-27)
Every PHP site on the server has the following minimum settings in its `phpIniOverride` block:
``` php_value upload_max_filesize 64M php_value post_max_size 128M php_value memory_limit 256M php_value max_execution_time 300 php_value max_input_time 300 ```
These were applied in bulk on 2026-05-27 after dm-ph.com reported `upload_max_filesize` errors (global php.ini default was 2M). All 25 PHP vhosts were patched in one pass.
Sites with this baseline confirmed:
| Site | Notes |
| —— | ——- |
| 24hrresponse.com | |
| aberdeenrda.co.uk | |
| bankhousecatering.co.uk | |
| billing.myretonmarquees.co.uk | |
| chippy.kaburu.co.uk | Also has `memory_limit 512M` in `.user.ini` (Divi) |
| claybusters.scot | |
| dm-ph.com | Trigger site — PHP 8.1 |
| doctorfin.co.uk | |
| friendscic.org | |
| ggsgenerators.co.uk | |
| invoice.kaburu.co | |
| jafricasafari.com | |
| kaburu.co | |
| kaburu.co.uk | |
| kaburusvr.uk | |
| l8.kaburu.co.uk | |
| l8waterhygiene.co.uk | |
| myretonmarquees.co.uk | |
| panel.kaburu.cc | |
| pilates-edinburgh.co.uk | |
| stats.kaburu.co | |
| test.kaburu.cc | |
| test1.kaburu.cc | |
| touchwell.co.uk | |
| womenunlimited.africa | Has debug.log deny context (2026-07-31 security fix) |
Sites with no PHP handler (no override needed):
| Site | Reason |
| —— | ——– |
| kaburu.cc | Proxy/monitor site — no PHP |
| search.kaburu.cc | Pure SearXNG reverse proxy |
### Editing a vhost PHP override
```bash # Edit nano /usr/local/lsws/conf/vhosts/SITE/vhost.conf
# Then graceful restart /usr/local/lsws/bin/lswsctrl restart ```
The `phpIniOverride` block sits near the top of vhost.conf, just after `scripthandler`. Sites with `open_basedir` set look like:
``` phpIniOverride { php_admin_value open_basedir “/tmp:$VH_ROOT” php_value upload_max_filesize 64M php_value post_max_size 128M php_value memory_limit 256M php_value max_execution_time 300 php_value max_input_time 300 } ```
Sites without `open_basedir` (e.g. invoice.kaburu.co, stats.kaburu.co):
``` phpIniOverride { php_value upload_max_filesize 64M php_value post_max_size 128M php_value memory_limit 256M php_value max_execution_time 300 php_value max_input_time 300 } ```
### Bumping limits for a specific site
If a site needs more than the baseline (e.g. video uploads, WooCommerce imports):
```bash nano /usr/local/lsws/conf/vhosts/SITE/vhost.conf # Change php_value upload_max_filesize to e.g. 256M # Change php_value post_max_size to e.g. 512M /usr/local/lsws/bin/lswsctrl restart ```
### Adding a new site
New sites created via CyberPanel get the global php.ini defaults (2M upload, 8M post). Always add the baseline `phpIniOverride` values immediately after site creation. This is part of the site migration checklist — see site-migration.
—
## Blocking specific files via vhost context
To block a specific file from being served (return 403), add an exact-path `context` block to the vhost config. Regex `~` context blocks are unreliable for arbitrary filenames — `\\.user\\.ini$` and `wp-config\\.php$` work, but `debug\\.log$` did NOT match on womenunlimited.africa. Use exact-path form:
``` context /wp-content/debug.log {
allowBrowse 0
accessControl {
deny *
}
} ```
After editing `/usr/local/lsws/conf/vhosts/SITE/vhost.conf`, run `/usr/local/lsws/bin/lswsctrl fullrestart` (not just graceful `restart` — graceful SIGUSR1 does NOT reliably pick up new contexts) and verify with `curl -sI https://SITE/path/to/file`.
For Cloudflare-proxied sites, also purge the URL via the CF API (`/zones/{zone_id}/purge_cache`) — single-URL purges can take 30s+ to propagate; `{“purge_everything”:true}` is more reliable for urgent fixes.
## What does NOT block log files (verified 2026-07-31)
Tested on womenunlimited.africa + friendscic.org — these approaches do NOT reliably block `.log` files:
| Approach | Result | |||||
| — | — | |||||
| `<FilesMatch “\.(log\ | sql\ | bak\ | backup\ | gz\ | tar)$”>Require all denied</FilesMatch>` in `.htaccess` | Ignored — file served with 200 |
| `<IfModule mod_rewrite.c>RewriteEngine On; RewriteRule | .*\.(log\ | …)$ - [F,L]</IfModule>` in `.htaccess` | Ignored — file served with 200 | |||
|---|---|---|---|---|---|---|
| Same RewriteRule at vhost.conf level | Ignored — file served with 200 | |||||
| `context ~ “/wp-content/.*\\.log$”` in vhost.conf | Ignored — regex `~` not matching arbitrary patterns | |||||
| `context /wp-content/debug.log` (exact path) in vhost.conf | WORKS — returns 403 |
Only the exact-path vhost context works for blocking specific files. For each log file type that needs blocking, you need a separate `context /wp-content/NAME.log { deny * }` block. For most sites, the existing `context ~ “wp-config\\.php$”` rule handles wp-config, and Wordfence WAF intercepts requests for `/wp-content/debug.log` by exact filename match — but other log files (plugin logs, error.log, etc.) are NOT automatically blocked.
Update 2026-07-31: All 25 WP sites now have the explicit `context /wp-content/debug.log { deny * }` block in vhost.conf (backups at `vhost.conf.bak-20260731-0501`). Vhost block is the primary defence; Wordfence is now belt-and-braces rather than sole protection.
If you need broad log-file protection on a site (no Wordfence, or want to block more than just debug.log), add individual `context` blocks per filename in vhost.conf. There is no clean way to do “all .log files” via LiteSpeed config alone.
## ⚠ Known quirks
1. public_html must be chmod 755 — LiteSpeed will not serve files otherwise (403 errors) 2. PHP path in vhosts — Must be `lsphp83` not `lsphpPHP83` — CyberPanel CLI gets this wrong, always verify after creation 3. SSL after DNS cutover — Re-issue via CyberPanel after pointing DNS; Cloudflare proxy means initial cert may be self-signed 4. Dotfiles (.user.ini) — `FilesMatch` .htaccess blocks are ineffective on LiteSpeed for dotfiles. Use `chmod 640` instead. Must be re-applied after each site migration (see hardening) 5. phpIniOverride `php_admin_value` — `open_basedir` uses `php_admin_value` (cannot be overridden by site). Other settings use `php_value`. Don't mix them up or LiteSpeed silently ignores the line 6. `.htaccess` `<Files>` with Apache 2.2 syntax — `Order deny,allow` / `Deny from all` is silently ignored by LiteSpeed. Use `<IfModule mod_authz_core.c>Require all denied</IfModule>` form, or block at vhost context level (more reliable)