Skip to content

Laravel deployment and long-running workers

Status: Complete. Last reviewed 2026-08-27.

A Laravel deployment is not complete when new PHP files exist on disk. Requests may run in short-lived PHP-FPM workers; queue workers, Horizon supervisors, Octane workers, Reverb servers, and schedule:work are long-lived processes that boot the application and retain code and memory. OPcache may also retain compiled code according to its validation and reload configuration. Every runtime needs an explicit release switch or graceful restart.

Prefer immutable releases: build dependencies and frontend assets, validate configuration, run tests, generate release-local Laravel caches, then atomically switch a current-release pointer or replace instances. Mutating a shared directory in place can pair old processes with new vendor code or new routes with old controllers. Build artifacts using production-like environment input without leaking secrets into a broadly distributed artifact.

Laravel’s optimize command caches configuration, events, routes, and views; optimize:clear removes generated caches. Route closures can prevent route caching. Cache generation is not a database migration or response cache. Include a release identifier in health responses and logs so mixed fleets are visible.

During a rolling or zero-downtime deploy, old and new releases coexist. Migrations must normally be backward compatible with both. Use expand-and-contract: add nullable columns or new tables/indexes first; deploy code that can read/write both representations; backfill in bounded resumable work; switch reads after evidence; later stop old writes; only then enforce constraints or remove old schema in a separate release.

Renaming or dropping a column in the same release that changes code is unsafe because old workers can still reference it and rollback may restore old code. A large blocking index or backfill inside a migration can exceed deploy time and lock production traffic. Understand the database engine’s online-DDL behavior, separate long data movement, and observe replication lag. migrate --force removes an interactive prompt; it does not make a migration safe.

Queue payloads are a release compatibility contract. A job may wait until after several deploys. Keep serialized fields compatible, prefer scalar stable identifiers, and make handlers tolerate rows changing or disappearing. Renaming a job class or changing constructor shape can strand existing payloads. Drain, migrate, or provide compatibility shims deliberately.

queue:work is long-lived and does not notice changed code after boot. queue:restart writes a restart signal in the cache; workers check it between jobs and exit gracefully, so a supervisor must start replacements. The cache must be shared and reachable by all workers. The command does not kill an executing job. Supervisor stop grace must exceed the worker/job timeout relationship, and the process manager must send and escalate signals predictably.

Horizon provides its own supervisors and metrics for Redis queues. horizon:terminate asks the master process to exit after current work; an external supervisor restarts it on the new release. Horizon balancing, timeouts, tries, queue allocation, and maintenance behavior are production configuration, not dashboard cosmetics. Monitor oldest-job age, throughput, failures, runtime percentiles, worker exits, and memory—not merely queue length.

A safe order depends on the change, but typically deploy compatible schema first, publish the immutable release, warm caches, switch web traffic, restart long-lived workers, verify versions/health, then contract later. If new jobs require new code, ensure capable workers exist before new producers dispatch them. If old producers can still enqueue, new consumers must understand old payloads.

FPM commonly recycles request workers, limiting how long leaked application state survives. Queue and Octane processes handle many operations in one process. Static properties, mutable singletons, facade-resolved instances, global locale/timezone, Carbon test time, tenant context, log context, database transaction state, and third-party SDK clients can leak between jobs or requests.

Use container scopes for per-request/per-job services and clear explicit global state in lifecycle hooks. Do not inject a request or application container into an Octane singleton: the captured object can be stale on later requests. Avoid accumulating unbounded arrays, listeners, or model graphs. Worker recycling by max jobs, max time, or Octane request limits bounds damage but does not make leakage correct. Add tests that process two different tenants sequentially in one process.

Octane boots the application once and serves requests with Swoole, Open Swoole, RoadRunner, or FrankenPHP workers. It resets framework-managed state between requests but cannot know every static or third-party global. Concurrent tasks add another constraint: code must not assume mutable process state belongs to one request. Octane reload/restart belongs in the deploy sequence, and its worker/request counts need capacity evidence.

Health checks should distinguish liveness from readiness. A process can be alive but unable to resolve configuration, query the database, reach a required dependency, or serve the intended release. Keep readiness checks bounded and avoid amplifying an outage through expensive probes. Remove an instance from traffic before termination, allow in-flight requests to finish, then enforce a finite grace period.

Laravel maintenance mode can return 503s and can use a shared cache driver across hosts. It is useful for truly incompatible maintenance, but it is not a substitute for compatible deployment. Queued jobs pause in maintenance mode unless forced; scheduled tasks are also skipped unless allowed. Pre-rendering a maintenance response protects the path while dependencies are being updated.

Rollback means code, schema, configuration, caches, assets, and queued payloads remain compatible with the previous release. A destructive migration or one-way data rewrite can make “switch the symlink back” fictional. Define roll-forward and rollback thresholds, preserve the prior artifact, and test them. Validate after deployment with release-aware HTTP, queue, scheduler, storage, and dependency smoke checks.

Current: Laravel 13 documents production cache optimization, health routing, reload commands, queue restart, Horizon termination, and Octane lifecycle caveats. Common: the same worker-restart and expand/contract principles apply to Laravel 11–12. Legacy: mutable shared-host deployments and manual FPM/worker restarts remain common; improve them incrementally by adding release identity, supervisors, compatible migrations, and scripted restart/verification.

Generic release, rollback, and operational principles belong in deployment, observability, and operations.