Install
The installer checks the architecture and glibc (2.34 or newer: RHEL/Alma/Rocky 9 and 10, Ubuntu 22.04+), verifies the release checksum, backs up httpd_config.conf and any previous module, adds the module hatch block, and does a graceful restart. If OpenLiteSpeed does not come back with hatch loaded, it restores the previous module and config on its own.
curl -fsSL https://raw.githubusercontent.com/prabowosd/hatch/main/install.sh | sudo bash
sudo bash install.sh --file ./libhatch.so # install a local build
sudo bash install.sh --version v0.1.0 # pin a release
sudo bash install.sh --no-guard # skip the Webuzo config guard
sudo bash install.sh --no-watch # skip the .htaccess watcher
sudo bash install.sh --status
sudo bash install.sh --uninstall
The config block that gets added:
module hatch {
ls_enabled 1
}
Supported directives
RequestHeader cannot be implemented: LSIAPI can read a request header but has no call that changes one. The line is logged and skipped. The next release (v0.2) adds Basic Auth: AuthType, AuthUserFile, Require valid-user.
| Directive | Notes |
|---|---|
Header set|append|merge|add|edit|unset | always and onsuccess accepted. set replaces values the server added itself; edit substitutes a pattern in the value already set, and edit* replaces every match. env=name and env=!name conditions apply; expr= is reported and ignored. |
SetEnv, UnsetEnv | Sets and clears request environment variables, so the backend and the rewrite engine see them. |
SetEnvIf, SetEnvIfNoCase, BrowserMatch, BrowserMatchNoCase | Matches a request header, Request_URI, Request_Method, Remote_Addr, Remote_Host or Host. !var and var=value are accepted. |
php_value, php_admin_value, php_flag, php_admin_flag | Needs CyberPanel's OpenLiteSpeed core, whose API carries the php config calls. On other cores the line is logged once and skipped. |
ErrorDocument | A path target, for a 404 and for a denied request, and for any other 4xx or 5xx the backend returns (401, 500, 503). The page is served through an internal redirect and keeps the original status. URL and text targets need a body hatch cannot write, so they are logged and skipped. |
Options -Indexes | A directory with no index file answers 403 instead of listing its contents. +Indexes restores the server default. Symlink and content-negotiation options (FollowSymLinks, SymLinksIfOwnerMatch, MultiViews) are accepted and left to the server. |
DirectoryIndex | Serves the first listed file that exists, e.g. DirectoryIndex index.php index.html; disabled turns it off. Only plain file names are accepted. |
<Limit>, <LimitExcept> | Access rules apply only to the listed HTTP methods (or to every method except them). |
Satisfy Any|All | Combines host rules (Order/Allow/Deny) with Require rules using OR (Any) or AND (All). |
AddDefaultCharset | Adds a charset to the response Content-Type when the response has none; Off disables it. |
SSLRequireSSL | Answers 403 when the request did not arrive over TLS. |
Redirect, RedirectPermanent, RedirectTemp | Prefix match that keeps the rest of the path, like mod_alias. Status words permanent, temp, seeother, gone. |
RedirectMatch | Regex with $1..$9. Targets must be a path or an http(s) URL. |
Order, Allow from, Deny from | Apache 2.2 semantics, IPv4, IPv6, CIDR and partial addresses (10.1). |
Require all granted|denied, Require ip, Require not ip | Apache 2.4 semantics. |
<Files>, <Files ~>, <FilesMatch> | Applied to the requested file name. |
<IfModule> | Contents apply; <IfModule !...> fallbacks are skipped. |
ExpiresActive, ExpiresDefault, ExpiresByType | access plus N unit and A<seconds> forms, type/* wildcards. Like mod_expires, a PHP or proxied response that sets its own Cache-Control or Expires keeps it; Header set Cache-Control still wins over expiry. |
CacheEnable, CacheDisable, CacheMaxExpire | Emits X-LiteSpeed-Cache-Control so the LiteSpeed cache engine caches matching URLs (public,max-age=N, private,max-age=N, or no-cache). Paths are matched at or below the prefix; the last matching directory rule wins. A backend header already set wins, a backend that set its own Cache-Control or Expires keeps it, and Header set X-LiteSpeed-Cache-Control wins over the directives. |
RewriteEngine, RewriteRule, RewriteCond, RewriteBase | Left to OpenLiteSpeed, untouched. |
Unsupported directives are logged once per file change and skipped; they never break the site.
How it works
hatch hooks URI_MAP (redirects and access control) and SEND_RESP_HEADER (headers, expiry and LiteSpeed cache control). For each request it walks from the docroot to the file's folder and merges every .htaccess on the way: headers accumulate from parent to child, the deepest access rules win, the deepest matching redirect wins.
Parsed files are cached per worker and re-read only when their mtime or size changes. Files over 256 KB are ignored. Regexes use the regex crate, which runs in linear time, so a hostile pattern cannot stall a worker. Header names, values and redirect targets are validated; anything that could inject a header or a javascript: redirect is dropped. Every hook runs inside catch_unwind and fails open: a bug in hatch skips the .htaccess rules for that request instead of taking the server down.
The server's function table is read once per worker. CyberPanel's core has a longer lsi_api_t, so hatch picks the layout from the core's ABI marker first and falls back to scanning the server binary.
CyberPanel
CyberPanel is supported from v0.1.2. CyberPanel replaces OpenLiteSpeed with its own core (2.5.x) whose lsi_api_t carries three extra members; hatch detects that layout at load time. v0.1.1 and older crash the worker on the first request there, so upgrade before installing on a CyberPanel server.
hatch recognises the CyberPanel core by the cyberpanel_lsi_api_abi_v1 symbol the core exports and reads the table with that layout. On a core with an unknown marker it logs one line and stays idle instead of guessing. The bundled cyberpanel_ols module needs a paid license and stays inert without one; hatch runs next to it.
Webuzo
Webuzo 4.x runs a stock OpenLiteSpeed core, so hatch loads on it unchanged: there is no ABI marker to read and no special build. Its vhosts use configFile and autoLoadHtaccess 1, with document roots under /home/<user>/<domain>/public. hatch asks the server for the document root, so the .htaccess beside the site is found either way.
What differs is who owns the config. Webuzo writes httpd_config.conf and every vhost under conf/webuzo_vh/ from its own state, on a certificate renewal, a domain or account change, and sometimes on its own service check, so a module hatch block added by hand can disappear. When the installer sees Webuzo (/usr/local/webuzo or conf/webuzo_vh/), it also installs /usr/local/bin/hatch-guard with a one-minute entry in /etc/cron.d/hatch-guard. The guard puts the block back and restarts OpenLiteSpeed only when Webuzo has removed it; a config that still has the block is left alone.
RewriteRule is the one family OpenLiteSpeed keeps for itself: it parses .htaccess at config load and caches the rules, so a rewrite edit waits for a reload. Webuzo compensates with a two-minute cron that reloads when a .htaccess is newer than cgid; CyberPanel does the same every three minutes. On Webuzo the installer also adds hatch-reload, a systemd service that watches the document roots from conf/webuzo_vh/ with inotifywait, ignores every file except .htaccess, debounces two seconds, and reloads at most once every ten seconds. Rewrite edits apply within seconds instead of minutes, and the service re-reads the vhost list every five minutes so new sites are covered. It needs inotify-tools.
The directives hatch handles itself (Header, Redirect, Require, FilesMatch, Expires and the rest) never wait for a reload: they are read from .htaccess on each request, so an edit applies to the next request.
php_value and php_flag are logged and skipped on Webuzo's stock core, the same as on any OpenLiteSpeed build without CyberPanel's API. OpenLiteSpeed has no module API for per-request PHP configuration; phpIniOverride in a vhost or context block is the static alternative, and Webuzo keeps per-domain files for it under /var/webuzo-data/openlitespeed/custom/.
Docker
install.sh edits an OpenLiteSpeed installed on the host. For a container, mount the release .so and a config that enables it instead.
services:
openlitespeed:
image: litespeedtech/openlitespeed:latest
volumes:
- ./hatch-x86_64-linux-gnu.so:/usr/local/lsws/modules/hatch.so:ro
- ./httpd_config.conf:/usr/local/lsws/conf/httpd_config.conf:ro
Use the x86_64 or aarch64 asset that matches the container. The image must use glibc (the official OpenLiteSpeed images do); a musl/Alpine image cannot load the module.
Build from source
The module links against OpenLiteSpeed's LSIAPI (ols-api/, GPLv3, from OpenLiteSpeed 1.8.3). Builds run in a container so the result matches the target glibc.
podman build -t hatch-build -f build/Containerfile build
podman run --rm -v "$PWD":/src:Z hatch-build cargo build --release
# target/release/libhatch.so
Test
podman run --rm -v "$PWD":/src:Z hatch-build cargo test # unit tests
tests/integration.sh target/release/libhatch.so # real OpenLiteSpeed in a container
HATCH_CYBERPANEL=1 tests/integration.sh libhatch.so # CyberPanel's OpenLiteSpeed core (x86_64)
HATCH_LOCAL=1 tests/integration.sh libhatch.so # the OpenLiteSpeed installed on this host
CI runs all of them on x86_64 and aarch64: OpenLiteSpeed 1.8 (official image), 1.9 on AlmaLinux 10 (LiteSpeed RPM), and CyberPanel's core built from build/CyberPanelfile.
Troubleshooting
When the module loads but a rule does not apply, create /usr/local/lsws/conf/hatch.debug and restart OpenLiteSpeed. hatch then logs, per request, what the server handed it: the URI, the doc root, the resolved file path and how many .htaccess files apply.
sudo touch /usr/local/lsws/conf/hatch.debug
sudo /usr/local/lsws/bin/lswsctrl restart
tail -f /usr/local/lsws/logs/error.log | grep '\[hatch\]'
The check runs once per worker thread, so the restart is required after creating or removing the file. Entries are written at WARN level to the vhost error log, so an errorlog block with logLevel ERROR will suppress them. Remove the file and restart when done, since an active trace writes several lines per request.
License & sponsors
GPL-3.0-or-later, the same as OpenLiteSpeed. hatch is an independent project and is not affiliated with LiteSpeed Technologies or CyberPanel.
Kenari sponsors hatch through its grant program for two months, until December 2026.