Installing HiLMS without Docker
For a machine that already runs PHP-FPM and a web server. Commands are for Debian 13 and
Ubuntu 24.04; read requirements.md first, storage.md if media will live in a bucket,
languages.md if this installation will speak more than one language, mcp.md if AI agents will author courses here, and theming.md if
this installation runs a theme of its own.
Packages
Section titled “Packages”PHP 8.5
Section titled “PHP 8.5”Debian and Ubuntu do not ship 8.5 yet, so take it from the sury repository:
sudo apt-get updatesudo apt-get install -y ca-certificates apt-transport-https lsb-release curl gnupgcurl -fsSL https://packages.sury.org/php/apt.gpg | sudo gpg --dearmor -o /usr/share/keyrings/sury-php.gpgecho "deb [signed-by=/usr/share/keyrings/sury-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \ | sudo tee /etc/apt/sources.list.d/sury-php.listsudo apt-get update
sudo apt-get install -y \ php8.5-fpm php8.5-cli php8.5-common php8.5-opcache \ php8.5-bcmath php8.5-curl php8.5-gd php8.5-intl php8.5-mbstring \ php8.5-mysql php8.5-redis php8.5-xml php8.5-zipctype, dom, exif, fileinfo, filter, iconv, json, libxml, openssl, pcntl,
pcre, pdo, posix, session, simplexml, sodium, tokenizer, xmlreader,
xmlwriter and zlib come with those packages. Confirm with php -m.
Then set the ini values from requirements.md in
/etc/php/8.5/fpm/conf.d/99-hilms.ini and /etc/php/8.5/cli/conf.d/99-hilms.ini:
memory_limit = 256Mmax_execution_time = 60upload_max_filesize = 100Mpost_max_size = 100Mdate.timezone = UTCopcache.enable = 1opcache.validate_timestamps = 0The database
Section titled “The database”Either MySQL 8.4 from the MySQL APT repository, or MariaDB 11 from the distribution:
sudo apt-get install -y mariadb-server mariadb-clientsudo mariadb -e "CREATE DATABASE hilms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"sudo mariadb -e "CREATE USER 'hilms'@'localhost' IDENTIFIED BY 'a-long-password';"sudo mariadb -e "GRANT ALL ON hilms.* TO 'hilms'@'localhost';"Set DB_CONNECTION=mariadb for MariaDB, mysql for MySQL. The dump client must match:
mariadb-dump or mysqldump.
sudo apt-get install -y redis-serversudo sed -i 's/^# *maxmemory-policy .*/maxmemory-policy noeviction/' /etc/redis/redis.confsudo sed -i 's/^# *maxmemory .*/maxmemory 256mb/' /etc/redis/redis.confsudo systemctl restart redis-servernoeviction matters: the queue lives in Redis, and an evicted job is a lost job. The
limit matters too: a full Redis refuses writes, which HiLMS rides out on the database, while
an unbounded one grows until the kernel kills the largest process on the machine — often the
database. 256 MB is plenty; the Redis memory health check warns at 80 % of it.
The rest
Section titled “The rest”sudo apt-get install -y nginx composer git unzipsudo apt-get install -y jpegoptim optipng pngquant webp # optional, smaller imagesNode 24 is only needed where the assets are built. Build them on a workstation and copy
public/build across if you would rather not install Node on the server.
The application
Section titled “The application”sudo mkdir -p /var/www/hilms && sudo chown "$USER" /var/www/hilmsgit clone [email protected]:pipejesus/hilms.git /var/www/hilmscd /var/www/hilmsgit checkout v0.6.0
composer install --no-dev --optimize-autoloadernpm ci && npm run build # or copy public/build from elsewhere
cp .env.production.example .env # then edit itphp artisan hilms:installhilms:install checks the machine, generates the key, migrates, seeds the roles and
permissions, asks which time zone the panel is read in (or takes it from --timezone),
asks for the first administrator (or takes it from HILMS_ADMIN_*), links public storage,
generates the API keys, warms the caches and runs the health checks.
Ownership: the web user must own storage and bootstrap/cache.
sudo chown -R www-data:www-data /var/www/hilms/storage /var/www/hilms/bootstrap/cacheserver { listen 443 ssl http2; server_name lms.example.com;
root /var/www/hilms/public; index index.php;
ssl_certificate /etc/letsencrypt/live/lms.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/lms.example.com/privkey.pem;
client_max_body_size 100m; charset utf-8;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { fastcgi_pass unix:/run/php/php8.5-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; }
location ~ /\.(?!well-known).* { deny all; }
error_page 404 /index.php;}The Caddy equivalent:
lms.example.com { root * /var/www/hilms/public encode gzip php_fastcgi unix//run/php/php8.5-fpm.sock file_server request_body { max_size 64MB }}Handing private lesson media to nginx
Section titled “Handing private lesson media to nginx”Course material — the media library’s files that only a lesson may show — lives outside the
document root and is streamed by PHP by default.
On a large installation, let nginx serve the bytes instead: set MEDIA_SENDFILE=nginx
and add an internal location to the server block. Nothing reaches it from the outside;
the application names the file after it has checked the student’s access. Only the
local lessons disk is handed over this way; course material kept on any other disk is
streamed by PHP, or sent to its bucket with a five-minute address (storage.md).
location /_lessons/ { internal; alias /var/www/hilms/storage/app/private/lessons/;}Caddy has no X-Accel-Redirect; leave MEDIA_SENDFILE=php there. The Docker image
ships the nginx snippet already (/etc/nginx/server-opts.d/lessons.conf), so setting
MEDIA_SENDFILE=nginx is all a Compose stack needs.
PHP-FPM pool
Section titled “PHP-FPM pool”In /etc/php/8.5/fpm/pool.d/www.conf, size the pool to the memory you have: each child
peaks near memory_limit. On a 4 GB machine, pm = dynamic, pm.max_children = 10,
pm.start_servers = 2, pm.min_spare_servers = 1, pm.max_spare_servers = 3.
Horizon
Section titled “Horizon”Horizon must run as one supervised process. With supervisor:
; /etc/supervisor/conf.d/hilms-horizon.conf[program:hilms-horizon]process_name=%(program_name)scommand=php /var/www/hilms/artisan horizonautostart=trueautorestart=trueuser=www-dataredirect_stderr=truestdout_logfile=/var/log/hilms-horizon.logstopwaitsecs=3600sudo supervisorctl reread && sudo supervisorctl updateThe systemd equivalent:
; /etc/systemd/system/hilms-horizon.service[Unit]Description=HiLMS queue workersAfter=network.target redis-server.service
[Service]User=www-dataRestart=alwaysExecStart=/usr/bin/php /var/www/hilms/artisan horizonExecStop=/usr/bin/php /var/www/hilms/artisan horizon:terminateTimeoutStopSec=3600
[Install]WantedBy=multi-user.targetThe scheduler
Section titled “The scheduler”sudo crontab -u www-data -e* * * * * cd /var/www/hilms && php artisan schedule:run >> /dev/null 2>&1Everything operational hangs off that minute: the health heartbeats, the nightly backup at 01:30, its cleanup and monitor, the audit-log and failed-job pruning. Without it the health page turns red within minutes, which is the point.
Upgrading
Section titled “Upgrading”See upgrade.md.
OpenLiteSpeed
Section titled “OpenLiteSpeed”Untested, but the shape is known. Install lsphp85 and the same extension packages
(lsphp85-common, lsphp85-mysql, lsphp85-intl, lsphp85-redis, …), point the virtual
host document root at /var/www/hilms/public, let it read the shipped public/.htaccess for
the rewrite, raise Max Request Body Size to 64M, and make sure the front proxy sends
X-Forwarded-Proto. Report anything that differs so this section can lose its warning.
HiLMS is MIT-licensed. No replicants were harmed in the writing of these books.