Health-gated deployments: what they are and why you need them
CI passed. Tests green. Forge clicked deploy. Two minutes later the homepage loads but /api/checkout returns 500 on every request because a migration did not run and a config cache still references yesterday's .env. Traffic is already on the broken release. Rollback works — if someone is awake to notice.
Health-gated deployments mean production traffic stays on the previous known-good release until post-deploy checks pass. CI green is necessary; production healthy is sufficient to cut over.
The problem health gating solves
Traditional deploy scripts assume success when:
git pullcompletescomposer installexits zerophp artisan migrate --forceruns (maybe)php artisan config:cachesucceeds
None of these prove HTTP works, queues process jobs, or error rates stayed flat. Laravel hides failures behind cached config, failed queue jobs accumulate silently, and Octane workers serve stale code until restart.
Without gating, you discover regressions from customers or generic uptime monitors that only hit /. By then you are rolling back under pressure instead of never promoting the bad build.
What health gates actually check
A useful gate combines synthetic probes and production signals:
HTTP smoke tests — hit critical routes with expected status codes: /, /login, /health, a read-only API endpoint. Authenticated flows can use a dedicated smoke-test user.
Queue health — confirm Horizon or supervisor workers are running and queue depth is not climbing abnormally after deploy.
Error rate baseline — compare 5xx rate to pre-deploy window; spike beyond threshold fails the gate.
Process health — PHP-FPM accepting connections, Redis reachable, MySQL connection count stable.
Deploy marker correlation — timestamp the release so dashboards show exactly when behavior changed.
Gates should run after migrations and cache clears, with a short soak window (two to ten minutes) to catch slow leaks.
Manual implementation on a single server
If you deploy with Forge or a shell script, add an explicit verification step before switching symlink or reloading traffic:
#!/bin/bash
set -e
cd /home/forge/example.com
php artisan down --retry=60 || true
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan queue:restart
# Health gate — fail deploy if checks do not pass
for i in {1..30}; do
if curl -sf https://example.com/health > /dev/null; then
break
fi
sleep 2
done
curl -sf https://example.com/health || exit 1
curl -sf https://example.com/login || exit 1
php artisan up
Improve this with authenticated API checks and Horizon status:
php artisan horizon:status | grep -q running || exit 1
On failure, keep artisan down or revert the release directory before opening the site. Document rollback in the same script — gates are useless without automatic retreat.
Blue-green or canary setups on multiple nodes extend the same idea: load balancer only routes to new targets when health checks pass. DigitalOcean load balancers and AWS ALB both support target group health checks — wire them to a real /health endpoint, not TCP-only probes.
Why /health must be honest
A health endpoint that always returns 200 is theater. Laravel health checks should verify database, Redis, and optionally queue connectivity:
Route::get('/health', function () {
DB::connection()->getPdo();
Redis::connection()->ping();
return response()->json(['status' => 'ok']);
});
Extend with disk space and queue depth thresholds if you expose metrics internally.
Document rollback in the same runbook as deploy — who approves, how to revert migration edge cases, how to communicate client-facing downtime. Health gates fail more often when rollback is scary.
Common mistakes teams make
Gating on CI smoke tests only — CI runs against seed data; production has ten million rows and timeouts CI never saw.
Zero soak time — memory leaks and connection pool exhaustion appear five to fifteen minutes after cut-over.
Ignoring queue workers — HTTP probes pass while Celery or Horizon is down.
No deploy marker in dashboards — on-call wastes twenty minutes proving the release caused the spike.
Fix these before buying louder paging tools.
For Laravel Forge and Envoyer users, wire deploy webhooks to your monitoring layer so every release carries a UUID and timestamp in the dashboard — future you will thank present you during the first post-deploy 502 scare.
Health gating is cheap insurance compared to rollback under fire during a client demo or Black Friday traffic spike — implement before you need it, not after.
How Reflex implements health gating
Reflex Pipeline records deploy markers from your CI or Forge webhook. After promotion, reflexd reports PHP-FPM status, queue depth, error signals, and HTTP probe results. Pipeline can block or roll back when post-deploy regression exceeds policy — tying cut-over to real production data, not script exit codes.
The Brain repair cycle handles secondary failures after a good deploy — opcache stale, workers not restarted — see how the Brain repair cycle works. Pair gating with alert fatigue reduction so failed gates notify once with context, not five channels.
Laravel monitoring guide covers ongoing signals after go-live. View pricing for Pipeline on Studio+ plans. Compare Reflex vs DIY deploy scripts if you maintain bash gates by hand today.
Ready to stop firefighting your servers?
Try Reflex free for 14 days.