Modernizing the PHP runtime on a multi-tenant LSWS box is rarely an “all sites at once” affair. Some sites are on a custom theme that uses PHP 4-style class constructors. Some have a plugin still calling create_function(), removed in PHP 8. Some are pinned to a specific PHP minor by a vendor’s compatibility matrix. The path forward isn’t waiting until every site is portable — it’s running PHP 8.2 by default and quietly leaving one or two stragglers on PHP 7.4 until you can patch them.
OpenLiteSpeed makes this nearly free. The mechanism is two extprocessors at the server level and a single scripthandler override in whichever vhosts need the legacy runtime. Here’s how it slots together.
Install both PHP versions
Add the LiteSpeed apt repo if you haven’t, then install the version you’re moving to. The 7.4 build that shipped with OLS is already in /usr/local/lsws/lsphp7/; we just need 8.2 alongside it:
curl -fsSL https://repo.litespeed.sh | sudo bash
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y \
lsphp82 lsphp82-common lsphp82-mysql lsphp82-curl lsphp82-imagick \
lsphp82-intl lsphp82-opcache lsphp82-imap
# Verify
/usr/local/lsws/lsphp82/bin/lsphp -v
# PHP 8.2.30 (litespeed) ...The extensions mbstring, xml, zip, gd, and bcmath are bundled into the main lsphp82 package on the LiteSpeed apt repo — there’s no lsphp82-mbstring like Ubuntu’s php-* naming. Don’t go hunting for them.
Wire up the wrapper symlink
OLS’s existing extprocessor lsphp uses the path fcgi-bin/lsphp7, which is a symlink to the 7.4 binary. Mirror that pattern for 8.2 so the config reads naturally:
sudo ln -sfn /usr/local/lsws/lsphp82/bin/lsphp \
/usr/local/lsws/fcgi-bin/lsphp82
ls -la /usr/local/lsws/fcgi-bin/lsphp82
# lsphp82 -> /usr/local/lsws/lsphp82/bin/lsphpDefault everything to 8.2, define a legacy override
In /usr/local/lsws/conf/httpd_config.conf, change the existing extprocessor’s path to point at the new wrapper, and add a second extprocessor for the 7.4 fallback. Keep the same socket pattern so worker management stays predictable:
extprocessor lsphp {
type lsapi
address uds://tmp/lshttpd/lsphp.sock
maxConns 10
env PHP_LSAPI_CHILDREN=10
env LSAPI_AVOID_FORK=200M
initTimeout 60
retryTimeout 0
persistConn 1
respBuffer 0
autoStart 2
path fcgi-bin/lsphp82 # ← was fcgi-bin/lsphp7
backlog 100
instances 1
}
extprocessor lsphp_legacy {
type lsapi
address uds://tmp/lshttpd/lsphp7.sock
maxConns 5
env PHP_LSAPI_CHILDREN=5
env LSAPI_AVOID_FORK=200M
initTimeout 60
retryTimeout 0
persistConn 1
respBuffer 0
autoStart 2
path fcgi-bin/lsphp7
backlog 100
instances 1
}The server-level scripthandler still says add lsapi:lsphp php — every site uses the default 8.2 runtime unless its vhost says otherwise.
Pin the legacy site at the vhost level
In conf/vhosts/<legacy-site>/vhconf.conf, append a single block:
scripthandler {
add lsapi:lsphp_legacy php
}That’s the entire override. Vhost-level scripthandler overrides server-level. Every .php request to that one vhost goes through the 7.4 socket; everything else stays on 8.2.
Restart and verify
Graceful reload does not reliably rebind extprocessors when you’ve added a new one. Use a hard restart:
# Kill any stale workers from prior masters that might still hold sockets
sudo killall -9 openlitespeed lsphp lsphp7 lsphp82 2>/dev/null
sudo rm -f /tmp/lshttpd/lsphp.sock /tmp/lshttpd/lsphp7.sock /tmp/lshttpd/*.pid
sudo systemctl start lshttpd
sleep 5
# Trigger one request per site so workers spawn
for h in main-site.com legacy-site.com; do
curl -s -o /dev/null -k --resolve "$h:443:127.0.0.1" "https://$h/"
done
# Check which lsphp binary each worker is running
for pid in $(pgrep -f lsphp); do
echo " $pid → $(sudo readlink /proc/$pid/exe)"
doneYou should see two distinct binaries in the output: workers for the legacy vhost executing lsphp-7.4.x, workers for everything else executing the 8.2 binary under /usr/local/lsws/lsphp82/bin/lsphp.
Two gotchas
- LiteSpeed Cache will serve cached pages with no PHP involvement. If you check
x-powered-byafter the switch and seePHP/7.4.xon a site you expected to be on 8.2, purge LSCache (rm -rf /usr/local/lsws/cachedata/*) before assuming the config is wrong. The cached HTML still has the old header baked in. - The 8.2 build defaults to
expose_php = Off, so on the upgraded sitesx-powered-byis simply absent. That’s the desired behavior; don’t try to “fix” it back.
When you’re ready to retire 7.4
Patching the legacy site is usually one of two things:
- A theme widget calling
$this->WP_Widget(...)in a PHP4-style constructor — convert to__construct()+parent::__construct(...)(a few lines per widget). - A plugin calling
create_function(),each(), or hitting one of the dropped silent-string-conversion warnings. Either patch with a small monkeypatch or replace the plugin.
Once the legacy site loads cleanly under 8.2, delete the scripthandler block from its vhconf and restart. The site falls back to the server-level default, the lsphp_legacy extprocessor goes idle, and you can drop it from httpd_config.conf on the next maintenance window.
The whole pattern is genuinely two config blocks plus one symlink. The hard part is knowing it’s possible at all — most “PHP version” howtos pretend you have to upgrade everything in lockstep. You don’t.
