Deploying Laravel Yourself, Without the Two-Minute Outage
A managed platform, a provisioning service or a plain server - and the four things that decide whether a deploy is invisible, none of which are the hosting choice.
There are three reasonable ways to run a Laravel application, and the argument about which one usually hides the fact that the deploy script matters more than the choice.
The three options, honestly
A managed platform. You push, it builds and runs. The least operational work available, at the highest per-unit price, with the platform's constraints as your constraints. Good for a team with no operations capacity and a workload that fits.
A provisioning service on your own servers. Something that configures a server you own and gives you deploys, certificates and workers without you writing the Ansible. This is where most Laravel applications live and it is a sensible default: you keep the machine, you skip the setup.
A plain server you configure. Cheapest, most control, and it is only cheap if someone owns it. Updates, certificates, backups, monitoring - the invoice is small and the responsibility is not.
There is no wrong answer here. There is only an unowned one.
What actually decides whether a deploy hurts
1. Atomic releases. Deploy into a new directory and move a symlink when it is ready. Copying files over a running application means requests are served by a half-updated codebase for a few seconds, which produces errors nobody can reproduce afterwards.
releases/2026-09-21-140233/
current -> releases/2026-09-21-140233
Shared between releases: storage/ and the .env. Everything else is new
each time, and rollback is moving the symlink back.
2. The cache rebuild, in order.
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cacheRun these on the new release before it becomes current. config:cache in
particular means env() returns null everywhere outside config files - which
is correct behaviour and surprises people, so keep env() in config files
only.
3. Restarting queue workers. The step most often missing:
php artisan queue:restartA worker boots your application once and keeps it. Without this, your web tier runs the new release and your queue tier runs whatever was current when those processes last started - which is its own class of bug.
4. Migrations, separated by risk. Routine ones in the deploy. Anything that rewrites a large table run deliberately and watched, because the lock outlasts the request timeout and the deploy will report success while the site returns errors.
The order that keeps the site up
# in the new release directory, before it is live
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan config:cache && php artisan route:cache && php artisan view:cache
# make it current
ln -sfn "$RELEASE" current
# after
php artisan migrate --force
php artisan queue:restart
sudo systemctl reload php8.3-fpmReload rather than restart, so in-flight requests finish. And check afterwards rather than trusting the script:
ps -eo lstart,cmd | grep "[q]ueue:work"Start times older than the deploy mean the restart did not reach them.
The pieces people forget to set up at all
The scheduler needs one cron entry, and only one, on one machine:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
Two servers both running it means every scheduled task runs twice.
Workers need a supervisor. queue:restart stops workers; it does not start
them. Without systemd or Supervisor watching, a deploy quietly leaves you with
none and jobs pile up until somebody notices.
Backups need a restore. A backup that has never been restored is a hypothesis. Restore one into a scratch environment, on a schedule, and write down how long it took - that number is your actual recovery time.
Logs need somewhere to go. The default daily file on the application server is fine until you have two servers, at which point an incident means reading two sets of files by hand.
The part that is not about hosting
Nothing above depends on which of the three options you chose. A managed platform handles some of it for you; a plain server means you write it once. Either way the deploy either restarts the workers or it does not.
Which is why the most useful thing to do with this article is not to change hosting. It is to read your own deploy script and find which of these four steps is missing - in our experience it is usually the worker restart, and it has been missing since the day the script was written.
