Running with FrankenPHP
FrankenPHP is a modern PHP application server that bundles the webserver (Caddy) and the PHP runtime into a single binary — replacing the typical nginx + PHP-FPM pair with one process and giving you automatic HTTPS out of the box. Koel ships a Caddyfile.example at the project root that wires it up correctly.
Note that FrankenPHP is only the runtime — Koel itself still needs to be installed first, with vendor/ populated and the frontend built into public/build/. As such, a pre-compiled archive is the recommended path when pairing with FrankenPHP: it ships both folders pre-built, so the FrankenPHP binary becomes your only runtime dependency — no system PHP, Composer, Node, or pnpm needed on the host. Of course, building from source still works if you already have that toolchain installed.
Install FrankenPHP
Pre-built binaries are published at frankenphp.dev/docs/#install. On Linux, the one-line installer fetches the right binary for your architecture:
curl https://frankenphp.dev/install.sh | sh
sudo mv frankenphp /usr/local/bin/Configure the Caddyfile
From the Koel project root:
cp Caddyfile.example CaddyfileEdit Caddyfile and replace localhost with the domain you'll serve from. Using a real public domain enables automatic HTTPS via Let's Encrypt.
Run it
frankenphp runThat's it — Koel is now served on port 443 (and 80, redirected to HTTPS).
Running artisan commands
FrankenPHP bundles its own PHP, exposed via the php-cli subcommand. Any php artisan … from Koel's CLI documentation becomes:
frankenphp php-cli artisan <command>For example, frankenphp php-cli artisan migrate runs database migrations and frankenphp php-cli artisan koel:sync triggers a media scan. For convenience: alias artisan='frankenphp php-cli artisan'.
If Koel was installed alongside a system PHP (used by composer install, etc.), running php artisan <command> against the system PHP works too — both PHPs share the same .env and database.
DB_HOST=localhost gotcha
FrankenPHP's bundled PHP has a different compiled-in default MySQL socket path than the system PHP your distro ships. If .env has DB_HOST=localhost, Laravel connects via Unix socket — and frankenphp php-cli will look at the wrong path. Symptom: Checking database connection … ERROR when running koel:init under FrankenPHP even though HTTP serving works fine. Fix either of:
- Override per-command:
DB_HOST=127.0.0.1 frankenphp php-cli artisan <command> - Or change
.envtoDB_HOST=127.0.0.1to force TCP for everyone (small loopback overhead, but uniform).
For FrankenPHP CLI options not covered here (worker mode, multi-domain serving, custom PHP flags, etc.), refer to the official FrankenPHP documentation.
Run as a systemd service (Ubuntu)
For a production deployment, run FrankenPHP under systemd so it starts on boot and restarts on failure. Create /etc/systemd/system/koel.service:
[Unit]
Description=Koel (FrankenPHP)
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/koel
ExecStart=/usr/local/bin/frankenphp run
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.targetAdjust WorkingDirectory and User to match where Koel lives on your host. Then enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now koel
sudo journalctl -u koel -fBehind a reverse proxy
If you're already running nginx (or another reverse proxy) and want to use FrankenPHP only as the PHP runtime, make two changes to your Caddyfile:
- Uncomment the
auto_https offandservers { trusted_proxies … }block at the top —Caddyfile.exampleships it commented and ready for this case. Adjust the trusted-proxies CIDR if the reverse proxy lives on a different host. - Bind the site block to a loopback port — change
localhost {to:8001 {and addbind 127.0.0.1as the first line inside the block. Everything else inside the site block stays as-is.
Then point your existing reverse proxy at 127.0.0.1:8001 and let it terminate TLS as before.