Web Development 5-8 minutes

Laravel Octane in Laravel 13: Speed Up Your App with FrankenPHP, Swoole, or RoadRunner

Diego Cortés
Diego Cortés
Full Stack Developer & SEO Specialist
Share:
Laravel Octane in Laravel 13: Speed Up Your App with FrankenPHP, Swoole, or RoadRunner
Image generated with AI

With PHP-FPM every request boots and tears down the framework; with Laravel Octane the app boots once and stays in memory, with 3-4x gains in throughput and TTFB. This guide covers installation, servers, memory, and production.

Why PHP-FPM Leaves Performance on the Table

The Lifecycle of a Request in PHP-FPM

In a classic setup, each request executes index.php from scratch: the framework loads, every service provider boots, the dependency container resolves, the response is generated, and when it ends the process dies or returns to the FPM pool with its memory released. That full bootstrap repeats on every single request, and it is exactly the cost you pay in TTFB even when your business logic takes milliseconds.

What Changes with Octane: Workers That Stay in Memory

Octane flips the model: the application initializes once and the worker keeps listening for requests for its entire lifetime. Subsequent requests reuse the already-booted framework, skipping the provider loading and container resolution. The result is much lower response time and higher throughput, because the expensive startup work is paid once per worker instead of once per request.

Choosing a Server: FrankenPHP vs Swoole vs RoadRunner

FrankenPHP: The Recommended Default (Go + Caddy)

Since Laravel 11, FrankenPHP is the server Octane recommends by default. It is a binary that combines Go and Caddy with a PHP worker mode, so it serves HTTP, handles TLS certificates, and runs your application in a single process. For most projects it is the simplest option to operate, because it does not require installing extra PHP extensions.

Swoole and Open Swoole: The Highest Ceiling and the Steepest Cliff

Swoole and Open Swoole are PHP extensions that add a coroutine scheduler. They deliver the best performance of the three and access to Octane-exclusive APIs, but they also demand more care: you must install or compile the extension, understand how coroutines interact with the rest of your code, and operate a more demanding runtime. If you do not need their special features, you pay complexity without using it.

RoadRunner: The Go Binary Without Coroutines

RoadRunner is an application server written in Go that talks to PHP workers. It is a solid choice if your team already works with Go or you prefer a binary independent of the PHP runtime. It does not offer Swoole's coroutine APIs, but it provides persistent workers with a good balance between performance and operational simplicity.

Installation in Laravel 13

composer require laravel/octane and octane:install

Installation starts with the official package and the interactive command that configures your chosen server:

composer require laravel/octane
php artisan octane:install --server=frankenphp

You can pass --server=swoole, --server=open-swoole, or --server=roadrunner depending on what you decided in the previous section. The command publishes the required configuration and, for FrankenPHP, downloads the binary for your platform.

Starting the Server: octane:start and Its Options

With the server installed, you start it with octane:start. The following example listens on all interfaces on port 8080:

php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8080

If you use FrankenPHP and need to customize Caddy behavior (middleware, routing, or your own directives), you can pass your own Caddyfile with --caddyfile=/path/Caddyfile.

Workers and max-requests: The Safety Net Against Leaks

Octane starts several workers to take advantage of the machine's cores. You control them at startup:

php artisan octane:start --workers=4 --task-workers=6 --max-requests=500

--max-requests defines how many requests a worker serves before gracefully restarting; the default is 500. It is your main safety net against memory leaks: even if something in your code retains memory between requests, the worker recycles when it hits the limit and the leak never grows forever.

The Memory Trap of Persistent Workers

Why Static Properties Stay Alive Between Requests

In PHP-FPM this problem does not exist because the process dies at the end of each request. With Octane, the worker stays alive and everything that outlives the request accumulates. A static property, a poorly managed singleton, or a cache stored in a class variable persists for the entire life of the worker. This is the tradeoff behind much of the performance gain: state that is not cleaned up stays around, for better and for worse.

What Laravel Resets and What the Developer Must Reset

Laravel and Octane reset the framework state between requests: the container, facades, sessions, and the application's own services are restored automatically. What they do not touch is the state you create: static properties on your classes, closures captured in outer scope variables, or in-memory caches. If your code stores something in a static property expecting it to disappear, it will not disappear under Octane. The practical rule is to audit every static usage and every custom singleton, and rely on --max-requests as emergency containment. There are documented production cases where a static-property cache that never purged ended up crashing the server; the fix involved resetting that state explicitly.

Swoole-Only APIs: Octane::concurrently, Ticks, and Cache

If you choose Swoole or Open Swoole, Octane exposes functions that do not exist on the other servers. The most useful is Octane::concurrently(), which runs several closures in parallel inside a single worker and returns all results when they finish, ideal for firing multiple queries or external calls at once. You also get ticks and intervals for recurring tasks, and an Octane cache with in-memory tables shared across workers. FrankenPHP and RoadRunner cannot offer these APIs because they lack Swoole's coroutine scheduler; if you need them, that is the reason to pick Swoole.

Production: Nginx, systemd, and Zero-Downtime Deploys

Octane Behind Nginx as a Reverse Proxy

In production, Octane should not be exposed directly. Nginx acts as a reverse proxy and handles static assets while Octane listens locally. A minimal server block would look like this:

server {
    listen 80;
    server_name your-app.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~* \.(css|js|png|jpg|svg|woff2)$ {
        root /var/www/your-app/public;
        expires 30d;
    }
}

Managing the Process with systemd or Supervisor

Octane must run as a supervised service so it starts with the system and restarts if it crashes. A typical systemd service runs the octane:start command with your worker and max-requests options, defines the project user, and restarts the process on failure. Supervisor is the usual alternative if you already use it for other tasks; the idea is the same: a process with automatic restart and centralized logs.

Deploy Strategy with Gradual Worker Restarts

The tricky part of deployment is that workers keep serving the previous version until they restart. Instead of killing all processes at once, you restart them gradually so in-flight requests are not cut: Octane receives the signal, finishes workers one by one, and replaces them with the new version. In practice the deploy has two steps: update the code and run php artisan octane:reload (or restart the service in a controlled way) so workers load the changes without a downtime window.

When NOT to Use Octane

Octane is neither free nor universal. If your application relies heavily on static properties or mutable singletons, the migration can cost more than it saves. It is also a bad fit if you run heavy queue workers in the same process, because one task that blocks the event loop affects every request on that worker. And on shared hosting or platforms that do not allow persistent processes, Octane has nowhere to live. Evaluate first: if your bottleneck is the database or business logic, the application server will not fix it.

Conclusion

Laravel Octane is the direct way to stop paying the framework bootstrap on every request: choose FrankenPHP to start simple, Swoole if you need coroutines, and always control your workers with max-requests. Before shipping to production, audit your statics, put it behind Nginx, and plan the gradual restart. If performance is your goal, you can also check how to cut TTFB with page caching or what Laravel 13 brought in its upgrade guide. Test Octane under real load and measure: the numbers will decide for you.

Categories