Deployment
PHP 8.3, MySQL and a web server whose document root points at public. Node is only needed to build assets, not to run them.
Requirements
| Component | Version |
|---|---|
| PHP | 8.3 or newer, with openssl, mbstring, pdo_mysql, zip, zlib, fileinfo, curl, bcmath |
| Database | MySQL 8 or MariaDB 10.6, over PDO |
| Web server | Apache 2.4 with rewrite, or nginx |
| Node | 20.19+ or 22.12+, only to build assets |
Install
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
# edit .env: APP_URL and the database credentials
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
chown -R www-data:www-data storage bootstrap/cache
find storage bootstrap/cache -type d -exec chmod 775 {} \;
APP_KEY is not just a session key here
It encrypts your application secrets and variable values in the database. Generate it once, back it
up with your database, and never run key:generate again on a live installation. Doing so
makes every stored secret and variable permanently unreadable.
Assets
public/build is gitignored, so a git pull will not bring it. Build it and ship
the output, which is the recommended route because the server then needs no Node at all:
npm ci && npm run build
rsync -av public/build/ user@server:/var/www/keyauth/public/build/
Or build on the server:
unset NODE_ENV
npm install --include=dev # not --omit=dev, Vite lives in devDependencies
npm run build
vite: command not found means devDependencies were skipped. Never copy
node_modules between operating systems either;
@tailwindcss/oxide ships a platform specific native binary. Copying
public/build is safe, since it is only static CSS, JS and fonts.
Apache
<VirtualHost *:443>
ServerName auth.example.com
DocumentRoot /var/www/keyauth/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/auth.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/auth.example.com/privkey.pem
<Directory /var/www/keyauth/public>
AllowOverride All
Require all granted
Options -Indexes -MultiViews +FollowSymLinks
</Directory>
<DirectoryMatch "^/var/www/keyauth/(?!public)">
Require all denied
</DirectoryMatch>
</VirtualHost>
The document root must be public. Pointing it at the project root
serves .env, and with it your APP_KEY and database password, over HTTP.
This is the single most common way a Laravel deployment leaks everything.
Scheduled maintenance
* * * * * cd /var/www/keyauth && php artisan schedule:run >> /dev/null 2>&1
This runs keyauth:prune hourly, closing dead sessions and trimming old activity logs and
expired blacklist entries. Without it those tables grow forever.
Behind a proxy
Set TRUSTED_PROXIES when something sits in front of PHP. Leaving it empty behind a proxy
makes every client IP read as the proxy address, which silently defeats IP blacklists and rate limits.
Setting it with no proxy in front lets clients spoof their IP through X-Forwarded-For. Both
directions are wrong, so match it to your actual topology.
First account
Registration is closed by default, but the very first account is always allowed and becomes an
administrator. Create it at /register, then keep
KEYAUTH_REGISTRATION_OPEN=false.