How to deploy a Laravel SaaS to production with zero downtime (2026 guide)
A practical guide to deploying Laravel SaaS applications with zero downtime, covering migration strategies, queue workers, health checks, and multi-tenant considerations.
Quick Answer
Quick answer: How to deploy a Laravel SaaS to production with zero downtime (2026 guide)
A practical guide to deploying Laravel SaaS applications with zero downtime, covering migration strategies, queue workers, health checks, and multi-tenant considerations.
See supporting documentationHow to deploy a Laravel SaaS to production with zero downtime (2026 guide)
Shipping a Laravel SaaS product is only half the challenge. The other half is deploying it without dropping active sessions, breaking billing webhooks, or corrupting tenant data mid-migration.
This guide covers a practical zero-downtime deployment strategy for Laravel SaaS applications, from migration safety to queue worker management to multi-tenant considerations.
Why zero-downtime matters more for SaaS
Traditional web apps can survive brief maintenance windows. SaaS products often cannot:
- Stripe webhooks arrive on their own schedule and cannot be paused.
- Tenants operate across time zones, so there is no universal quiet period.
- Downtime during checkout or billing cycles directly costs revenue.
- Team collaboration features mean multiple users are active simultaneously.
If your deployment process takes the app offline for even 30 seconds, you risk failed payments, lost form submissions, and broken trust.
The deployment pipeline at a glance
| Stage | What happens | Risk if skipped |
|---|---|---|
| Build | Install dependencies, compile assets, run static analysis | Broken frontend or missing packages in production |
| Pre-deploy checks | Run automated tests, verify environment config | Deploying broken code or missing secrets |
| Release swap | Symlink or atomic switch to new release directory | Partial deployments serving mixed old/new code |
| Migrate | Run database migrations against live database | Schema drift or failed migrations under traffic |
| Queue restart | Gracefully restart queue workers | Workers processing jobs with stale code |
| Health check | Verify application responds correctly post-deploy | Silent failures that only surface through user reports |
| Rollback plan | Keep previous release available for instant revert | Extended downtime while debugging a bad deploy |
Step 1: Structure releases for atomic swaps
Zero-downtime deployment starts with how you organize releases on the server.
Release directory pattern
/var/www/app/
├── releases/
│ ├── 20260401_120000/
│ ├── 20260401_140000/
│ └── 20260401_160000/ ← newest
├── current -> releases/20260401_160000/
├── storage/ ← shared across releases
└── .env ← shared across releasesThe current symlink points to the active release. Deploying means creating a new release directory, preparing it fully, then flipping the symlink. This is atomic at the filesystem level.
Shared resources
Storage directories (storage/app, storage/logs, storage/framework) and the .env file must be shared across releases. Symlink them into each release directory during setup.
ln -nfs /var/www/app/storage /var/www/app/releases/20260401_160000/storage
ln -nfs /var/www/app/.env /var/www/app/releases/20260401_160000/.envStep 2: Make migrations safe for concurrent traffic
Database migrations are the highest-risk step in any deployment. In a zero-downtime context, the old code is still serving requests while migrations run.
Rules for safe migrations
- Never drop a column in the same deploy that stops using it. First deploy code that no longer reads the column, then drop it in a subsequent release.
- Never rename a column directly. Add the new column, backfill data, update code to use both, then remove the old column later.
- Add columns as nullable or with defaults. This avoids table-level locks on large tables in MySQL/PostgreSQL.
- Use short, focused migrations. Long-running migrations block subsequent migrations and can cause connection timeouts.
Multi-tenant migration considerations
If you run database-per-tenant (multi_db mode), migrations must apply to every tenant database, not just the central one.
php artisan tenants:artisan "migrate --force" --tenant=allThis adds deployment time proportional to tenant count. For large tenant bases, consider:
- Running tenant migrations asynchronously via queued jobs.
- Batching tenants to avoid overwhelming the database server.
- Tracking migration status per tenant so partial failures can be retried.
Step 3: Manage queue workers gracefully
Queue workers run as long-lived processes. After a deploy, they still hold the old codebase in memory.
Graceful restart
php artisan queue:restartThis signals workers to finish their current job and then restart with fresh code. It does not kill jobs mid-execution.
Supervisor configuration
Use Supervisor (or systemd) to manage queue workers so they automatically restart:
[program:saas-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/current/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.logNote the path uses /current/ so it always resolves to the active release through the symlink.
Deployment-critical queues
If your app processes Stripe webhooks or sends transactional emails through queues, verify workers are healthy after every deploy. A silently dead worker can mean missed subscription renewals or lost invite emails.
Step 4: Environment configuration safety
Misconfigured environment variables are a leading cause of post-deploy incidents.
Pre-deploy config validation
Before flipping the release symlink, verify the critical variables exist:
php artisan config:cache
php artisan env:check APP_KEY DB_HOST STRIPE_SECRET MAIL_HOSTIf env:check is not available in your Laravel version, write a simple Artisan command that fails if required variables are empty.
Secrets rotation
When rotating API keys or database credentials:
- Update
.envon the server before deploying. - Run
php artisan config:clear && php artisan config:cacheafter the symlink swap. - Restart queue workers so they pick up new cached config.
Step 5: Health checks and smoke tests
A successful deploy means the symlink flipped without error. It does not mean the application is working.
Application health endpoint
Add a health check route that verifies core dependencies:
Route::get('/health', function () {
try {
DB::connection()->getPdo();
Cache::store()->get('health-check-ping');
return response()->json(['status' => 'ok'], 200);
} catch (\Throwable $e) {
return response()->json(['status' => 'error'], 500);
}
});Post-deploy verification
After the symlink swap, hit the health endpoint from your deploy script:
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health)
if [ "$HTTP_STATUS" != "200" ]; then
echo "Health check failed. Rolling back."
ln -nfs /var/www/app/releases/PREVIOUS_RELEASE /var/www/app/current
exit 1
fiMulti-tenant smoke test
For SaaS apps, also verify that at least one tenant context loads correctly. A central database connection succeeding does not guarantee tenant databases are reachable.
Step 6: Rollback strategy
Every deployment should have a one-command rollback path.
Instant rollback via symlink
Since the previous release directory still exists, rollback is a symlink swap:
ln -nfs /var/www/app/releases/PREVIOUS_RELEASE /var/www/app/current
php artisan config:cache
php artisan queue:restartMigration rollback limitations
Database rollbacks are harder. If the new release ran migrations that the old code does not expect, reverting the symlink alone is not enough. This is why forward-compatible migrations (Step 2) are critical. Design every migration so the previous release can still function against the migrated schema.
Keep N releases
Retain the last 3-5 releases on disk for quick rollback. Clean up older ones to save disk space:
ls -dt /var/www/app/releases/*/ | tail -n +6 | xargs rm -rfStep 7: CI/CD pipeline integration
Automate the full pipeline so deploys are repeatable and not dependent on manual SSH sessions.
Recommended pipeline stages
- Push to main triggers the pipeline.
- Test stage: run PHPUnit, static analysis (PHPStan/Larastan), and frontend build.
- Build stage:
composer install --no-dev,npm run build, cache config and routes. - Deploy stage: upload to server, create release directory, run migrations, swap symlink.
- Verify stage: health check, smoke test, notify team.
Deployment tools for Laravel
- Laravel Envoy: task runner built for Laravel deployments. Define deploy scripts in a Blade-like syntax.
- Deployer (deployer.org): PHP-based deployment tool with Laravel recipe.
- GitHub Actions + SSH: direct approach for smaller teams using CI to SSH and run deploy scripts.
All of these support the atomic symlink pattern described above.
Common deployment mistakes in SaaS
- Running
php artisan downduring deploys. This defeats zero-downtime. The maintenance mode approach is for planned maintenance, not routine releases. - Forgetting to restart queue workers. Workers silently run old code until manually restarted.
- Deploying destructive migrations without a compatibility window. Always separate schema removal from code removal by at least one release cycle.
- No health check automation. Manual verification after every deploy does not scale.
- Ignoring tenant database migrations. Central database migrates successfully but tenant databases fall behind.
Deployment checklist for Laravel SaaS
- Release directory structure with shared storage and .env
- Forward-compatible migration strategy
- Tenant database migration plan (if multi_db)
- Supervisor-managed queue workers with graceful restart
- Environment variable validation before symlink swap
- Health check endpoint with database and cache verification
- Automated rollback on health check failure
- CI/CD pipeline with test, build, deploy, and verify stages
- Release retention policy (keep last N releases)
- Post-deploy notification to team
Ship with confidence
Deployments should not be stressful events. With atomic releases, safe migrations, graceful worker restarts, and automated health checks, you can deploy your Laravel SaaS multiple times per day without impacting users.
If you want a production-ready foundation that is already structured for clean deployments, SaasForgeKit provides the baseline architecture so you can focus on your deployment pipeline instead of rebuilding infrastructure.
Ready to ship faster?
Build your SaaS with a production-ready foundation
Launch with authentication, billing, tenancy, and team workflows already in place, then focus on the features that make your product unique.
Related posts
Authentication, social login, and team invites in Laravel SaaS: the complete guide
How to build a complete auth system for Laravel SaaS products, covering registration, social login with Google and GitHub, email verification, team invitations, and role-based workspace access.
SaasForgeKit vs Laravel Spark vs Wave (2026): which starter kit should you choose?
A practical 2026 decision guide comparing SaasForgeKit, Laravel Spark, and Wave for teams choosing a Laravel SaaS foundation.
Laravel tenancy architecture decision framework for SaaS founders (2026)
A clear decision framework for choosing single-database vs multi-database tenancy in Laravel SaaS products, including migration triggers and tradeoffs.