Step 0: unattended security updates
Before anything else: a web server that never gets security updates is a problem waiting to happen.
sudo apt update && sudo apt full-upgrade
sudo apt install unattended-upgrades apt-listchanges
Enable security updates from stable + updates channels:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Un-comment the ${distro_codename}-updates line inside Unattended-Upgrade::Origins-Pattern.
sudo dpkg-reconfigure -plow unattended-upgrades
sudo unattended-upgrade -d # dry-run sanity check
Step 1: install OpenLiteSpeed and MariaDB
LiteSpeed maintains the official repo. Add it:
wget -qO - https://repo.litespeed.sh | sudo bash
Then install the server plus PHP 8.3 (the lsphp variant; adjust version to taste):
sudo apt install openlitespeed mariadb-server
sudo apt install lsphp83 lsphp83-common lsphp83-curl lsphp83-imagick \
lsphp83-imap lsphp83-intl lsphp83-mysql lsphp83-opcache \
lsphp83-redis lsphp83-memcached memcached
Set the WebAdmin password
sudo /usr/local/lsws/admin/misc/admpass.sh
Pick a strong username/password and write it down somewhere durable. The WebAdmin port defaults to 7080:
https://your-server-ip:7080
(Chromium may complain about the self-signed cert — click through, or fix it later by pointing it at your real Let's Encrypt cert once you have one.)
Secure MariaDB
sudo systemctl start mariadb
sudo mysql_secure_installation
Walk through the prompts: set a strong root password, disable anonymous users, disallow remote root login, drop the test database, reload privilege tables.
Step 2: create a virtual host
The idiomatic OpenLiteSpeed layout puts each site under /usr/local/lsws/conf/vhosts/. I prefer /var/www/<sitename>/ because it's where every other web server expects to find things — but OpenLiteSpeed's UI refuses to use that path directly. The fix is a symlink:
sudo mkdir -p /var/www/example.com/{conf,logs,html}
sudo chown -R lsadm:lsadm /var/www
sudo rm -rf /usr/local/lsws/conf/vhosts
sudo ln -s /var/www /usr/local/lsws/conf/vhosts
Now WebAdmin thinks the vhosts live under its own config tree, but they're actually at /var/www/.
In WebAdmin: Virtual Hosts → Add:
- Virtual Host Name:
example.com - Virtual Host Root:
/var/www/$VH_NAME - Config File:
$SERVER_ROOT/conf/vhosts/$VH_NAME/conf/vhconf.conf - Enable Scripts/ExtApps: Yes
Save. You'll be prompted to create the config file — click the link, confirm, save again.
Then on the vhost's General tab, set:
- Document Root:
$VH_ROOT/html
Save. Graceful restart (the spinning-arrow icon, top right of WebAdmin).
Step 3: listeners on 80 and 443
In WebAdmin → Listeners, delete the default Default listener on port 8088. Add two new listeners:
- HTTP: port 80, Secure No.
- HTTPS: port 443, Secure Yes.
For each, under Virtual Host Mappings → Add: vhost example.com, domains example.com, www.example.com.
Point DNS A-records for both names at the server IP, wait a minute for propagation.
Visit http://example.com — you should see an OpenLiteSpeed 404 (because the html directory is empty). That's the right kind of 404; it proves the routing works.
Step 4: Let's Encrypt with certbot
sudo apt install certbot
sudo certbot certonly --webroot \
-w /var/www/example.com/html \
-d example.com -d www.example.com
Enter your email, agree to terms, and certbot writes certs to /etc/letsencrypt/live/example.com/.
In WebAdmin, vhost → SSL:
- Private Key File:
/etc/letsencrypt/live/$VH_NAME/privkey.pem - Certificate File:
/etc/letsencrypt/live/$VH_NAME/fullchain.pem - Chain Certificate: Yes
Under SSL Protocols, enable TLSv1.2 and TLSv1.3 only. Uncheck the older protocols — none of them should be serving traffic in 2026.
Repeat the SSL settings for the HTTPS listener itself (vhost-level SSL overrides listener-level, but the listener needs a valid default cert for SNI hand-off). Note you can't use $VH_NAME in the listener config — spell the domain out.
Graceful restart. Visit https://example.com — padlock should be solid.
Auto-renewal with a post-renewal hook
certbot will auto-renew from its systemd timer; OpenLiteSpeed needs to reload to pick up the new cert. Add a deploy hook:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/openlitespeed-reload.sh <<'EOF'
#!/bin/sh
/usr/local/lsws/bin/lswsctrl restart
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/openlitespeed-reload.sh
sudo certbot renew --dry-run
The dry-run should simulate a renewal and call the hook without errors. Done — no more manual cron jobs.
Step 5: install phpMyAdmin
Grab the latest release:
sudo apt install unzip
cd /var/www
PMA_VER=$(curl -s https://www.phpmyadmin.net/home_page/version.json | grep -Po '"version":\s*"\K[^"]*' | head -1)
sudo wget "https://files.phpmyadmin.net/phpMyAdmin/${PMA_VER}/phpMyAdmin-${PMA_VER}-all-languages.zip"
sudo unzip "phpMyAdmin-${PMA_VER}-all-languages.zip"
sudo mv "phpMyAdmin-${PMA_VER}-all-languages" phpmyadmin
sudo rm "phpMyAdmin-${PMA_VER}-all-languages.zip"
sudo chown -R lsadm:lsadm phpmyadmin
Symlink it into your vhost (so the same phpMyAdmin can serve multiple sites):
cd /var/www/example.com/html
sudo ln -s /var/www/phpmyadmin phpmyadmin
Set up the blowfish secret for cookie auth:
sudo mv /var/www/phpmyadmin/config.sample.inc.php /var/www/phpmyadmin/config.inc.php
sudo nano /var/www/phpmyadmin/config.inc.php
# Find blowfish_secret line and replace with a 32-char random string:
# Generate one with:
# openssl rand -base64 32
$cfg['blowfish_secret'] = 'GENERATED_32_CHAR_STRING';
Create a dedicated phpMyAdmin user with only the privileges it needs (GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON app_db.* TO 'pma'@'localhost' IDENTIFIED BY '...'). Web-facing MySQL root credentials are a major blast-radius when they leak.
Visit https://example.com/phpmyadmin/ and log in.
Step 6: NinjaFirewall (optional WAF)
NinjaFirewall is a PHP application-level WAF — it intercepts requests before they reach your application via an auto_prepend_file hook. It catches a lot of opportunistic exploits (SQL injection patterns, file-inclusion attempts, known bad user-agents). The free WP Edition is good enough for most sites; the paid Pro expands to non-WordPress apps.
sudo mkdir -p /var/www/fw
cd /var/www/fw
# Download from https://nintechnet.com/ninjafirewall/pro-edition/#download
sudo wget -O fw.zip "https://nintechnet.com/ninjafirewall/pro-edition/?f=1"
sudo unzip fw.zip && sudo rm fw.zip
sudo chown -R lsadm:lsadm /var/www/fw
# Symlink into your vhost
cd /var/www/example.com/html
sudo ln -s /var/www/fw fw
# Permissions for NF's config/log dirs
cd /var/www/fw
sudo chmod -R 0755 conf nfwlog
Visit https://example.com/fw/install.php and walk through the installer — set an admin username/password, choose LiteSpeed as the SAPI, php.ini as the initialization file, and let it register.
Then wire up the auto_prepend_file so NF actually intercepts:
sudo nano /usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini
# Find the auto_prepend_file line, set:
auto_prepend_file = /var/www/fw/firewall.php
# And a .htaccess for belt-and-suspenders in case an admin disables auto_prepend in php.ini:
sudo tee /var/www/.htaccess <<'EOF'
# BEGIN NinjaFirewall
php_value auto_prepend_file /var/www/fw/firewall.php
# END NinjaFirewall
EOF
# Restart so the new php.ini takes effect
sudo /etc/init.d/lsws restart
Visit /fw/ — the NinjaFirewall dashboard should load and show the last few requests it's seen.
Step 7: force HTTPS
In the vhost's Rewrite tab, enable Rewrite and add:
rewriteCond %{HTTPS} !on
rewriteCond %{HTTP:X-Forwarded-Proto} !https
rewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
Graceful restart. All HTTP hits now bounce to HTTPS.
Step 8: keep OpenLiteSpeed itself updated
LiteSpeed ships an update script that can be scheduled. First ensure the user/group in the main config is set to lsadm (the default), then add to root's crontab:
sudo crontab -e
# Add:
30 2 * * * /usr/local/lsws/admin/misc/lsup.sh
Where to go from here
This gets you a working LAMP-like setup. Useful follow-ups:
- Enable OPcache in
php.ini(opcache.enable=1,opcache.memory_consumption=256) for a significant PHP speedup. - Wire up logrotate for OpenLiteSpeed's access and error logs (it ships
/etc/logrotate.d/openlitespeedbut not always — check). - Harden the WebAdmin interface: bind it to
127.0.0.1and access it over an SSH tunnel (ssh -L 7080:localhost:7080 user@server) rather than exposing it publicly. - Add fail2ban to block SSH/MySQL brute-force and any WordPress login attempts you see in access logs.
- Set up the security headers from my other tutorial.