Installation
From a clean host to a serving deployment, including the two keys you must generate before anyone signs up.
1. Get the code
git clone https://github.com/shortspan/shortspan.git
cd shortspan
composer install --no-dev --optimize-autoloader
npm ci && npm run buildNode is needed for the build only. The built assets go into public/build; no Node process runs in production.
2. Generate the two keys
Do this before anyone signs up. Both keys are permanent decisions.
cp .env.example .env
php artisan key:generate # APP_KEY - sessions and cookies
php artisan encryption:generate-key # SHORTSPAN_MASTER_KEY - the secret storeWhile you are here, set PASSKEYS_USER_HANDLE_SECRET explicitly. It defaults to APP_KEY, which couples passkey identity to a key you should be able to rotate.
3. Configure
APP_NAME=Shortspan
APP_ENV=production
APP_DEBUG=false
APP_URL=https://shortspan.example.com
DB_CONNECTION=pgsql
DB_HOST=…
DB_DATABASE=shortspan
DB_USERNAME=…
DB_PASSWORD=…
QUEUE_CONNECTION=database
CACHE_STORE=database
SESSION_DRIVER=database
MAIL_MAILER=resend
RESEND_API_KEY=…
MAIL_FROM_ADDRESS=noreply@shortspan.example.com
TRUSTED_PROXIES=…
DOCS_DOMAIN=docs.shortspan.example.com
DOCS_URL=https://docs.shortspan.example.comEvery variable is documented in Configuration reference. APP_DEBUG=true in production will leak configuration in stack traces, so check it.
4. Migrate
php artisan migrate --forceCreates the schema and backfills state for anything that needs it. On a fresh install there is nothing to backfill; on an upgrade there may be - see Upgrading.
5. Serve it
Point your web server at public/. A minimal nginx server block:
server {
listen 443 ssl http2;
server_name shortspan.example.com;
root /srv/shortspan/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* { deny all; }
}Serving the documentation on its own hostname means a second server block with the same root and a server_name matching DOCS_DOMAIN. The application routes by hostname; nothing else differs.
6. Start the worker and the scheduler
Neither is optional, and both fail silently. Run the worker under a supervisor:
[program:shortspan-worker]
command=php /srv/shortspan/artisan queue:work --tries=3 --timeout=90
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/shortspan-worker.log
stopwaitsecs=3600* * * * * cd /srv/shortspan && php artisan schedule:run >> /dev/null 2>&17. Cache the configuration
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache8. Create a platform administrator
php artisan shortspan:grant-platform-admin you@example.com --role=superThe control plane is at /admin, with its own login separate from the customer one. Being a workspace owner grants nothing there.
9. Verify
- 01
Load the application over HTTPS and sign up. Onboarding should complete cleanly.
- 02
Check that the verification email arrived. If not, the queue worker or the mail transport is the problem.
- 03
Store a secret and read it back. That exercises the whole encryption path.
- 04
Point the CLI at your host with
shortspan config set api_hostand runshortspan login. - 05
Open the control plane and check the operations page - every health probe should be green, and the scheduler heartbeats should be recent.
- 06
Confirm audit entries record real client addresses, not your proxy's. If they do not,
TRUSTED_PROXIESis wrong.
Something here wrong or missing? Tell us.