Octane and Long-Lived Processes is the chapter that treats Octane as an architectural change rather than a configuration flag. Under FPM your application is constructed and destroyed around every request, and every framework you have written against assumes it. Octane removes that assumption. What it gives back is boot time; what it asks for is that nothing holds state it should not.
Key takeaways
- Octane boots your application once and keeps it in memory, running each service provider's
registerandbootonly once per worker rather than once per request.- Laravel warns that injecting the request into a singleton leaves it holding a stale instance where all headers, input and query string data will be incorrect, as well as all other request data.
- Adding to a static array leaks, so Octane gracefully restarts any worker once it has handled 500 requests, adjustable with
--max-requests.- Workers default to one per CPU core, which is Chapter 2's pool with a different name and a lower default than most FPM pools.
- The Octane cache and Swoole tables are fast and volatile: Laravel documents that their data is flushed when the server restarts, so neither is a store of record.
Read this after Chapter 2, whose pool arithmetic decides your worker count, and after Chapter 6, because the Octane cache is one of the layers on that ladder and this chapter explains its lifetime. Chapter 11 covers the runtime underneath both, including the OPcache settings that make a cold worker expensive to start.
Octane goes in over a weekend. Response times improve on the staging dashboard, so it ships.
Two days later a customer reports seeing another customer's name on a confirmation screen. Intermittently. Nobody can reproduce it, because reproducing it needs two specific requests to land on the same worker in the right order.
The bug is one constructor. A service resolved during boot took the request object as a dependency, and it kept it.
Octane changes the lifetime model, not a speed dial
Under PHP-FPM the process model is the safety net nobody thanks. Every request constructs a container, resolves what it needs, and then the process is reset, so any state you accidentally kept is destroyed before it can hurt anybody.
Octane removes the reset. Laravel documents that it boots the application once, keeps it in
memory, and then feeds requests through it. Service provider register and boot methods run
once per worker rather than once per request.
So the correct way to describe Octane is not faster. It is longer lived. Every performance benefit follows from that, and so does every hazard, which is why the hazards are the bulk of this chapter.
The work Octane removes is boot: autoloading, provider registration, configuration and route resolution. It does not remove a query, a lock, a cache miss or a network wait. If your profile from Chapter 1 said the time is in the database, Octane has almost nothing to give you.
Three injections that hand you stale data
Laravel's Octane documentation names the hazard precisely, and it deserves quoting rather than paraphrasing because the wording is unambiguous.
If a service is resolved during the application boot process and the container is injected into it, that service holds the same container on subsequent requests. Injecting the request has a sharper consequence, in Laravel's words: all headers, input, and query string data will be incorrect, as well as all other request data.
The third case is the configuration repository, and it is the quietest of the three. A singleton that captured the config repository at boot will not see a value your code changed at runtime, which produces behaviour that is correct in tests and wrong in production.
The pattern that avoids all three is the same in each case. Do not capture the dependency, ask for it when you need it. A closure or a resolve call inside the method reads the current instance, where a constructor argument reads whichever instance existed at boot.
This is a correctness class of bug rather than a performance one. It is also the reason a production Octane rollout should be a code review pass over every singleton, not a deployment change.
A static array is a leak with a schedule
The second documented hazard is memory. Anything that accumulates without bound now accumulates for the life of the worker rather than the life of a request.
Laravel's documentation calls out adding to a static array as the canonical example, and static arrays are only the visible form. Any registry, any in-process cache without a size limit, any listener collection appended to per request, and any singleton that keeps a reference to something per-request will grow.
Octane's containment strategy is documented and blunt: it gracefully restarts any worker once it
has handled 500 requests, adjustable with --max-requests. That is the same idea as PHP's own
pm.max_requests for FPM, which the PHP manual describes as existing to work around memory leaks
in third-party libraries and which defaults to zero, meaning endless request processing.
Note the asymmetry, because it says something about intent. FPM ships with recycling off. Octane ships with it on, at 500. Octane's authors expected leaks.
Do not treat that as a fix. A worker restarting every 500 requests because of a leak is paying the boot cost you adopted Octane to avoid, so the restart is a safety net rather than a strategy.
The worker pool is Chapter 2's pool renamed
Laravel documents that Octane starts one application request worker per CPU core by default,
adjustable with --workers. Read that against Chapter 2 before you deploy it, because it is
usually a much smaller number than the FPM pool it replaced.
That is not a downgrade. Each worker now serves requests without rebooting, so its service time per request is lower and its throughput per worker is higher. What matters is the product, and the product needs checking rather than assuming.
There is a second pool on Swoole. Task workers, set with --task-workers, process background
operations, and they are the pool behind Octane::concurrently. Laravel also documents a default
maximum execution time of 30 seconds for incoming requests, which is a limit on how long one
request may hold a worker.
One operational requirement is easy to miss and produces the strangest bug reports. Because the
application is held in memory, deployed code is not live until the workers reload, which Laravel
documents as php artisan octane:reload. Add it to the deploy script the same day you add Octane.
| Server | What Laravel documents about it |
|---|---|
| FrankenPHP | Supports modern web features including early hints, Brotli and Zstandard compression |
| Swoole | Concurrent tasks, ticks and intervals, the Octane cache driver and Swoole tables |
| Open Swoole | Listed as a supported server alongside Swoole |
| RoadRunner | Listed as a supported server |
The table is deliberately thin. Anything more comparative would be a benchmark, and this book does not have one.
A long-lived process changes what a deploy is
Under FPM a deploy is complete when the files are in place, because the next request builds everything from those files. Under Octane it is not, and this is where a first rollout usually goes wrong operationally rather than in code.
The application in memory was constructed from the code and configuration that existed when the
worker booted. New files on disk change nothing until the worker is replaced, which is what
php artisan octane:reload does. A deploy that omits it produces the worst possible symptom: the
change is live on some workers and not others, so behaviour depends on which worker answered.
Two related facts from Laravel's deployment documentation matter here. php artisan optimize
caches configuration, events, routes and views, and once configuration is cached the .env file
is not loaded at all, so env() outside a config file returns null. Under Octane that cached
configuration is then held in memory for the worker's whole life, which compounds the same
mistake rather than creating a new one.
The other is the health route. Laravel serves one at /up, and under a long-lived process model
it is worth more than it is under FPM, because a worker can be alive and broken in ways a
per-request model would have cleared. A process that responds is not the same as a process whose
state is correct.
So treat reload as part of the deploy, not part of the recovery. Rolling it worker by worker keeps the pool serving, at the cost of two code versions being live at once, which is a trade you should name rather than discover.
Concurrency and the Octane cache are Swoole-only, with ceilings
Two Octane features are commonly cited as reasons to adopt it, and both come with documented limits that change how you design around them.
Octane::concurrently runs operations in parallel through task workers, and Laravel's
documentation states you should not provide more than 1,024 tasks due to limitations imposed by
Swoole's task system. Ticks and intervals, which run a callback every so many seconds, are
likewise Swoole features rather than Octane features.
The Octane cache driver is backed by Swoole tables. Laravel's documentation, fetched on 29 July 2026, claims read and write speeds of up to two million operations per second for it. That is Laravel's own published claim about their own driver, and it is not a measurement taken here.
The constraint attached to it is the one that decides where the driver belongs. Laravel documents that the cached data is shared across all workers on the server and will be flushed when the server is restarted, and that Swoole tables lose their data on restart too. So it is a per-server, per-boot cache.
Which places it exactly on Chapter 6's ladder. Above per-request memoisation, below Redis, never a store of record, and never a coordination primitive across servers.
A worked reading, composited and labelled
The details here are composited from ordinary production shapes rather than one system, and the shape is exact.
The question worth answering before adopting Octane is how much of your response is boot. The first reading profiles one request on the slowest route with Xdebug from Chapter 1, and separates the call tree into framework construction, application logic and I/O wait. Boot is the part Octane can remove. The other two it cannot.
The second reading is the same profile on your cheapest route, ideally something that returns a trivial response. On that route almost everything is boot, so it gives you the boot cost in isolation without any arithmetic.
Compare the two shapes and the decision makes itself. If boot is a large share of your slow route, Octane addresses your problem. If your slow route is dominated by database wait, Octane will improve the cheap route you were not worried about and leave the expensive one where it was.
The objection: Octane makes everything faster
This is the folklore and it is half true, which is what makes it durable. The honest version has three parts.
It removes boot from every request, which is real and measurable on your own system with the reading above. It does not remove I/O wait, which is where most slow Laravel routes actually spend their time, and Chapters 3 through 6 are about that. And it introduces a class of bug that FPM's process model was silently preventing for you.
There is a capacity subtlety too, which follows from Chapter 2. Lower service time per request raises the throughput of the same pool, so an Octane deployment can push more concurrent work into the database than the FPM deployment it replaced. Teams have adopted Octane and discovered lock contention, and concluded Octane caused it. Octane revealed it.
So the decision rule I would put in a runbook: adopt Octane when your profile shows boot is a
material share of the response, after a review pass over every singleton, with octane:reload in
the deploy and a plan for the connection pool downstream. Not as a first move. Never over a
weekend.
Chapter summary
Octane changes your application's lifetime model rather than turning up a speed dial: Laravel
documents that it boots the application once, keeps it in memory, and runs each service provider's
register and boot only once per worker, which removes autoloading, provider registration,
configuration and route resolution from every request and removes nothing else. The documented
hazards are correctness bugs first, and Laravel's wording on the request injection case is exact,
warning that all headers, input and query string data will be incorrect, as well as all other
request data, with the container and the configuration repository failing the same way, so the
pattern is to resolve dependencies when needed rather than capture them in a constructor. Memory
is the second hazard, with a static array as the canonical leak, contained rather than solved by
graceful worker restarts after 500 requests through --max-requests, which is a safety net that
reintroduces the boot cost you adopted Octane to avoid. Workers default to one per CPU core, which
is Chapter 2's pool with a smaller number and a lower service time, with Swoole task workers as a
second pool and a documented 30 second maximum execution time. Deployed code is not live until
php artisan octane:reload runs. Octane::concurrently is Swoole-only and documented as accepting
no more than 1,024 tasks, and the Octane cache, which Laravel's own documentation claims reaches up
to two million operations per second, is flushed when the server restarts, placing it above
per-request memoisation and below Redis on Chapter 6's ladder. And because lower service time
raises the throughput of the same pool, Octane can reveal downstream contention that FPM was
throttling for you.
Once the process stops dying, the bytes you send are the next place to look, and they are the one place you can remove work entirely rather than make it faster. Chapter 9 is HTTP: Payloads, Compression, and the Edge.
Sources
- Laravel OctaneLaravel · 2026-07-29 · Official documentation · verified
- DeploymentLaravel · 2026-07-29 · Official documentation · verified
- PHP Manual: FPM configurationThe PHP Group · 2026-07-29 · Official documentation · verified