Mahbubur Riad
Back to blog
Laravel 9 min read

Laravel Octane with FrankenPHP: How to Make Your Laravel App 2–3x Faster

Jun 15, 2026 · Mahbubur Riad

PHP-FPM boots Laravel from scratch on every single request. Laravel Octane with FrankenPHP keeps the framework in memory and serves requests at a fraction of the cost. Here's how to set it up and what to watch out for.

Laravel Octane with FrankenPHP: How to Make Your Laravel App 2–3x Faster
On this page

Every PHP-FPM request starts the same way: boot the framework, register service providers, resolve the container, load configuration, run middleware — and only then execute your actual business logic. On a typical Laravel application that is 50 to 150 milliseconds of overhead before your code even runs. Multiply that across hundreds of concurrent requests and you are leaving a significant amount of performance on the table.

Laravel Octane solves this by booting Laravel once and keeping it in memory across requests. FrankenPHP is the modern application server that makes this practical to run in production without the operational complexity that Swoole historically required.

This guide covers what Octane actually does, how FrankenPHP compares to the alternatives, how to set everything up, and — critically — the stateful container pitfalls that catch developers off guard.


What is Laravel Octane?

Laravel Octane is an official first-party package that changes how Laravel handles requests. Instead of the traditional PHP-FPM model where the entire framework bootstraps fresh on every request, Octane starts a long-lived application server that:

  1. Boots Laravel once at startup
  2. Keeps the service container, routes, configuration, and middleware in memory
  3. Handles incoming requests using that already-booted application state
  4. Serves the response and resets only what needs resetting between requests

The result is that subsequent requests skip the bootstrap cost entirely. For most Laravel applications this delivers a 2x to 3x improvement in requests-per-second (RPS) and a significant reduction in p95 latency.

Octane supports three underlying application servers: FrankenPHP, Swoole, and RoadRunner. Each has different characteristics and trade-offs.


What is FrankenPHP?

FrankenPHP is a modern PHP application server written in Go, built on top of the Caddy web server. It was developed by Kévin Dunglas (creator of API Platform) and has seen rapid adoption since Laravel added official Octane support for it.

What makes FrankenPHP stand out among Octane's server options:

  • No PHP extension required — unlike Swoole, FrankenPHP is a standalone binary. No PECL installation, no rebuilding PHP.
  • Built-in HTTPS — it inherits Caddy's automatic SSL certificate management via Let's Encrypt.
  • Early hints (HTTP 103), Brotli and Zstandard compression out of the box.
  • Easiest production setup of the three Octane drivers — Laravel even downloads the FrankenPHP binary automatically when you choose it during octane:install.
  • Worker mode keeps PHP scripts in memory between requests, which is the core of the Octane performance gain.

When you install Octane and choose FrankenPHP as your server, Laravel handles the binary download. You do not need to manage it separately.


Octane Server Comparison

FrankenPHP Swoole RoadRunner
Language Go + PHP C++ extension Go
PHP extension needed ❌ No ✅ Yes ❌ No
Setup complexity Low Medium Medium
Async/coroutine support Limited ✅ Full Partial
Built-in HTTPS ✅ Yes ❌ No ❌ No
Best for Most teams, simple setup Max throughput, async I/O No-extension Go process manager

For the majority of Laravel projects — APIs, SaaS applications, web apps — FrankenPHP is the right starting point. Swoole is worth evaluating if you have coroutine-heavy workloads with many parallel external API calls or database queries.


Installation

Requires PHP 8.1+ and Laravel 10+. Works on Laravel 12 and 13.

Step 1: Install Octane

Bash
composer require laravel/octane

Step 2: Run the installer and select FrankenPHP

Bash
php artisan octane:install

When prompted to select a server, choose frankenphp. Laravel will automatically download the FrankenPHP binary to your project.

Step 3: Start Octane locally

Bash
php artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000

Or use the shorthand:

Bash
php artisan octane:frankenphp

You should see output confirming the server started with worker processes. Your application is now running under FrankenPHP.

Step 4: For development with auto-reload

Bash
php artisan octane:start --server=frankenphp --watch

The --watch flag uses file watchers to reload workers when your code changes — similar to how php artisan serve reloads automatically.


Configuration

Octane's config file is published to config/octane.php. Key options worth reviewing:

PHP
// config/octane.php

return [
    'server' => env('OCTANE_SERVER', 'frankenphp'),

    'workers' => env('OCTANE_WORKERS', 'auto'),
    // 'auto' uses the number of CPU cores
    // Set explicitly for production: typically 2x CPU cores

    'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
    // Workers recycle after this many requests to release leaked memory
    // Always set this. Do not leave it unbounded.

    'listeners' => [
        // Octane fires events between requests so you can clean up state
        WorkerStarting::class    => [...],
        RequestReceived::class   => [...],
        RequestTerminated::class => [...],
    ],
];

.env for production

INI
OCTANE_SERVER=frankenphp
OCTANE_WORKERS=8
OCTANE_MAX_REQUESTS=1000

The Most Important Thing to Understand: Stateful Containers

This is where most Octane bugs come from, and where the documentation deserves more emphasis.

Under PHP-FPM, every request gets a completely fresh container. Singletons are singletons only within the lifetime of one request. Static properties reset. Nothing carries over.

Under Octane, the container persists between requests. Anything bound as a singleton is shared across all subsequent requests in the same worker. This means:

PHP
// ❌ This will leak between requests under Octane
class SomeService
{
    private static $currentUser;

    public static function setUser($user)
    {
        static::$currentUser = $user;
    }
}

If a request sets $currentUser to User A, the next request on that worker will still see User A — unless something explicitly resets it.

What Octane handles automatically

Laravel's own core — Auth, Request, Session, Cache, DB — is reset between requests by Octane's request lifecycle hooks. You do not need to worry about these.

What you need to handle

Any custom singleton or static state your application introduces needs to be examined.

PHP
// config/octane.php — register listeners to clean up your own state
'listeners' => [
    RequestTerminated::class => [
        function (RequestTerminated $event) {
            // Reset any custom static state here
            MyCustomService::reset();
        },
    ],
],

Common problem patterns

PHP
// ❌ Storing request-specific data in a singleton
class TenantResolver
{
    protected ?Tenant $current = null;

    public function set(Tenant $tenant): void
    {
        $this->current = $tenant; // leaks to next request
    }
}

// ✅ Use Octane's flush callback or reset in RequestTerminated listener
PHP
// ❌ Caching the authenticated user in a static property
class CurrentUser
{
    private static $user;

    public static function get()
    {
        return static::$user ??= auth()->user(); // leaks
    }
}

// ✅ Call auth()->user() directly — Laravel resets Auth between requests

Benchmarks: Octane vs PHP-FPM

Real-world numbers vary significantly based on application complexity, server specs, and workload characteristics. Based on benchmarks published in April 2026 on identical $12/month servers:

Metric PHP-FPM Octane (FrankenPHP) Improvement
Requests/second ~380 RPS ~950 RPS ~2.5x
p50 latency 48ms 18ms ~2.7x faster
p95 latency 142ms 52ms ~2.7x faster
Memory per worker ~30 MB ~80 MB Higher (expected)

The throughput improvement is consistent across different application server drivers, with Swoole occasionally edging ahead for concurrency-heavy workloads. FrankenPHP lands in the same range and wins on setup simplicity.

Memory usage is higher under Octane — the application stays loaded. On a server with 4 GB RAM running 8 workers, expect roughly 640 MB committed to PHP workers. This is expected and the trade-off is well worth it for most production setups.


Production Setup on a VPS (Nginx + Octane)

When running Octane on a VPS behind Nginx (or in aaPanel with a custom Nginx proxy), you run Octane on a local port and Nginx proxies requests to it.

Nginx config for Octane

NGINX
server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate     /etc/ssl/certs/yourdomain.pem;
    ssl_certificate_key /etc/ssl/private/yourdomain.key;

    location / {
        proxy_pass         http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}

Supervisor config to keep Octane running

INI
[program:laravel-octane]
command=php /var/www/yourdomain.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000 --workers=8 --max-requests=1000
directory=/var/www/yourdomain.com
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/octane.log
stopwaitsecs=30

Then reload Supervisor:

Bash
supervisorctl reread
supervisorctl update
supervisorctl start laravel-octane

Deployment Considerations

When you deploy new code, the Octane workers are still running the old version. You need to restart them after deployment.

Bash
# In your deploy script, after composer install and artisan commands:
php artisan octane:reload

# Or via Supervisor:
supervisorctl restart laravel-octane

If you use Jenkins or a similar CI/CD pipeline, add octane:reload as the final step after config:cache and route:cache.

Bash
# Example deploy sequence
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
php artisan octane:reload   # ← restart workers with new code

Should You Use Octane for Every Laravel Project?

Not necessarily. Octane adds operational complexity — you have a long-running process to manage, memory usage is higher, and stateful singleton bugs are a real risk for teams unfamiliar with the model.

Good candidates for Octane:

  • High-traffic APIs and SaaS applications where latency and throughput matter
  • Projects already well-tested and with minimal custom static state
  • Teams comfortable managing Supervisor and deployment restarts
  • Applications where the bootstrap overhead is a measured bottleneck

Not the right fit (yet):

  • Small personal projects or low-traffic sites where PHP-FPM is already fast enough
  • Applications with complex multi-tenant singleton patterns that have not been audited
  • Teams new to Laravel who are still learning the framework fundamentals
  • Legacy applications with heavy use of global state or static properties

For new Laravel 12 or 13 projects starting fresh, FrankenPHP as the Octane driver is a sensible default. The setup is straightforward and the performance headroom is useful to have before you need it.


Production Checklist

Before going live with Octane + FrankenPHP, run through these:

  • OCTANE_MAX_REQUESTS is set (1000 is a reasonable starting point)
  • OCTANE_WORKERS is set explicitly (not relying on auto in production)
  • Custom singletons and static state audited for cross-request leakage
  • Supervisor or process manager configured with autorestart=true
  • Deploy script includes octane:reload after code push
  • Nginx proxy_read_timeout set appropriately for your slowest routes
  • X-Forwarded-For and X-Forwarded-Proto headers forwarded correctly
  • Memory usage monitored on first production deployment
  • --max-requests log reviewed during initial traffic — watch for memory growth

Conclusion

PHP-FPM has been reliable for decades, but the framework bootstrap cost is real and measurable. Laravel Octane with FrankenPHP eliminates it by keeping the framework in memory and serving requests from long-lived workers. The performance improvement — consistently 2x to 3x in throughput and latency — is one of the most impactful changes you can make to an existing Laravel application without touching your business logic.

FrankenPHP is the right starting point for most teams. No PHP extensions, automatic binary installation, built-in HTTPS, and a setup that takes minutes rather than hours. The stateful container model requires careful attention, but with a solid deployment workflow and the max_requests safety valve, it is production-ready and increasingly the standard approach for serious Laravel applications in 2026.


Frequently Asked Questions

Is Laravel Octane stable for production?

Yes. Octane is an official Laravel package maintained by the core team. FrankenPHP, Swoole, and RoadRunner are all production-grade. Many high-traffic Laravel applications run on Octane in production.

Does Octane work with Laravel Queues and Horizon?

Yes. Octane handles web requests. Your queue workers still run as separate processes under Supervisor and are unaffected by Octane. Horizon continues to work normally alongside an Octane web server.

Can I use Octane with aaPanel?

Yes. Configure Octane to listen on a local port (e.g., 127.0.0.1:8000), add a custom Nginx reverse proxy in aaPanel pointing to that port, and manage the Octane process via Supervisor. The same principle as a manual VPS setup.

What happens if a worker crashes?

Supervisor restarts crashed workers automatically. Octane also handles graceful shutdown — in-flight requests are completed before a worker stops. Setting stopwaitsecs=30 in Supervisor gives workers time to finish.

Does Octane work with Laravel Sanctum and API authentication?

Yes. Laravel's Auth facade and Sanctum's token lookup are reset between requests by Octane's lifecycle hooks. There is no cross-request authentication leakage from Laravel's own auth layer.

How is FrankenPHP different from using Nginx directly?

FrankenPHP is a PHP application server — it runs PHP code directly in worker processes and keeps the application in memory. Nginx is a web server and reverse proxy that hands requests off to PHP-FPM (a separate PHP process manager). With Octane + FrankenPHP behind Nginx, Nginx handles SSL termination and static files, and FrankenPHP handles the PHP application layer.


  • Hetzner Cloud Review 2026: The Best Cheap VPS for Developers?
  • aaPanel Review: Best Free VPS Control Panel for WordPress, PHP and Laravel
  • How to Export a PDF File from Laravel Application Data

External Resources

Related

Related posts