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:

  1. HTTP: port 80, Secure No.
  2. 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';
Don't log in as MySQL root over the web

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/openlitespeed but not always — check).
  • Harden the WebAdmin interface: bind it to 127.0.0.1 and 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.