Self-Hosted VPS
Self-Hosted VPS
For servers you SSH into and control directly: DigitalOcean droplets, Hetzner, Linode, any bare-metal Ubuntu/Debian, etc.
Option A — systemd (recommended)
Create /etc/systemd/system/queue-autoscale.service:
[Unit]
Description=Queue Autoscale manager for Laravel
After=network.target redis-server.service
[Service]
Type=simple
User=forge
Group=forge
Restart=always
RestartSec=5s
WorkingDirectory=/home/forge/your-app.com/current
ExecStart=/usr/bin/php artisan queue:autoscale
StandardOutput=append:/var/log/queue-autoscale.log
StandardError=append:/var/log/queue-autoscale.err.log
# Graceful shutdown: SIGTERM, wait up to 60s, then SIGKILL.
# Match or exceed the queue's workers.shutdown_timeout_seconds.
TimeoutStopSec=60s
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable queue-autoscale
sudo systemctl start queue-autoscale
sudo systemctl status queue-autoscale
On deploy, signal the manager so it drains workers and exits. systemd will start it again from the current release:
php artisan queue:autoscale:restart
If the manager is wedged and does not exit, use sudo systemctl restart queue-autoscale as an operational fallback.
Option B — Supervisor
/etc/supervisor/conf.d/queue-autoscale.conf:
[program:queue-autoscale]
process_name=%(program_name)s
command=php /home/forge/your-app.com/current/artisan queue:autoscale
autostart=true
autorestart=true
user=forge
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/queue-autoscale.log
stopwaitsecs=60
stopsignal=TERM
Reload:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status queue-autoscale
Zero-downtime deploys
Either restart command works — most deploy scripts already run the first one:
php artisan queue:restart # standard Laravel deploy step, restarts workers AND the manager
php artisan queue:autoscale:restart # restarts only the autoscale manager
Your existing deploy script's php artisan queue:restart step is enough: spawned workers finish their current job and exit, the manager notices the same signal on its next evaluation tick, gracefully terminates any remaining workers, and exits. Your process supervisor then starts a fresh manager with the new code/config.
php artisan queue:autoscale:restart still works and restarts only the autoscale manager — useful when you also run separately-supervised queue:work daemons that should keep running.
Direct systemctl / supervisorctl restarts are still fine as manual fallbacks; they send SIGTERM immediately, and the manager performs the same graceful shutdown path.
Set QUEUE_AUTOSCALE_HONOR_QUEUE_RESTART=false to restore the old behaviour where only queue:autoscale:restart restarts the manager.
Log rotation
For systemd on a modern distro, journald handles rotation. If you append to a file (as shown above), drop a logrotate config:
/etc/logrotate.d/queue-autoscale:
/var/log/queue-autoscale*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
copytruncate
}
Troubleshooting
Manager keeps restarting every few seconds. Check the log with journalctl -u queue-autoscale -n 100.
The usual causes are a failure to acquire the host process lock (another manager is already running —
use queue:autoscale --replace), a missing .env, or queue-autoscale.enabled being false, which
makes the command exit immediately with an error.
Log fills with "Autoscale evaluation failed". Invalid queue configuration does not crash the
manager: each evaluation cycle is wrapped in a catch-all that logs the error and continues, so a bad
profile produces one error line per interval rather than a restart loop. Read the logged message —
it names the offending key, for example sla.target_seconds must be > 0. Invalid group config is
handled separately: it is logged once as critical and groups are then skipped until the manager is
restarted.
Workers spawn but die immediately. Run php artisan queue:work redis --queue=default manually as the same user. The error will be obvious (missing .env, wrong path, bad permissions).
Manager runs but nothing scales. Verify metrics: php artisan queue:autoscale:debug --queue=default
(add --connection=redis if the queue is not on your default connection). If metrics are empty,
laravel-queue-metrics isn't collecting — check its storage backend.