Cover image for Scaling Laravel 12 with Octane and FrankenPHP: A Production Performance Guide

At a glance

Reading time

~200 words/min

Published

3 weeks ago

May 22, 2026

Views

103

All-time total

Scaling Laravel 12 with Octane and FrankenPHP: A Production Performance Guide

PHP-FPM has served us well for fifteen years, but it boots Laravel from scratch on every request — 50 to 150ms wasted before your first line of business logic runs. Octane keeps the framework in memory and serves requests from a long-running worker process; FrankenPHP is the modern app server that makes Octane production-ready without the operational baggage of Swoole or RoadRunner. This guide is the recipe my team used to cut p95 latency by more than half.

What you will get from this guide

  • Why Octane + FrankenPHP is now the right default
  • A clean install path that does not break Forge or Vapor deploys
  • The "stateful container" gotchas that cause 90% of Octane bugs
  • Concurrent task dispatch with the new Concurrency facade
  • Real benchmark numbers and a deployment checklist
~5×

requests per second on the same hardware vs PHP-FPM

i

Info

What is Octane, in 30 seconds

A Laravel package that runs your app inside a worker process from FrankenPHP, Swoole, or RoadRunner. Each worker boots Laravel once and reuses the bootstrapped framework across thousands of requests — no rebooting per request, no opcache cold starts.

1. Why FrankenPHP is the right Octane backend in 2026

Pros

  • Single static binary — no Swoole compile, no RoadRunner sidecar
  • Built on the Caddy server: HTTPS, HTTP/3, and zstd compression for free
  • Worker mode supports the full PHP feature set (no async-only restrictions)
  • Native HTTP/2 push and 103 Early Hints for fast first paint

Cons

  • Newer than Swoole — community recipes are still catching up
  • Some legacy extensions assume process-per-request model
  • On Forge, you pick the FrankenPHP server type at provisioning

2. Installing on a fresh Laravel 12 app

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

# Local dev
php artisan octane:frankenphp --workers=auto --max-requests=500

# Production (Forge or Vapor handles this; for raw VPS:)
php artisan octane:frankenphp --host=0.0.0.0 --port=8000 --workers=8 --max-requests=1000

Warning

max-requests is your safety net

Workers leak memory eventually — every framework does. Setting <code>--max-requests=1000</code> recycles each worker after handling 1000 requests, releasing leaked memory automatically. Do not skip this.

3. The stateful container: where Octane bites you

Under PHP-FPM, every request gets a fresh container. Under Octane, the container persists. Anything you bind as a singleton — including authenticated user, current request, locale — is reused across requests unless you reset it. This is the source of the most surprising Octane bugs.

// BAD: this singleton survives across requests
$this->app->singleton(ReportBuilder::class, function ($app) {
    return new ReportBuilder($app['request']->user());
});
// First request: built with User A. Every subsequent request: still User A.

// GOOD: bind as scoped, reset between requests
$this->app->scoped(ReportBuilder::class, function ($app) {
    return new ReportBuilder($app['request']->user());
});

Danger

Audit your service providers before going live

Open every <code>app/Providers/*</code> file. Every <code>singleton</code> that touches request, user, locale, session, or cache should be <code>scoped</code> instead. Mixing them up causes one user to see another user's data — silently.

4. Concurrent dispatch with the Concurrency facade

Octane unlocks parallel work that simply does not exist under PHP-FPM. The Concurrency facade dispatches multiple closures and waits for all of them.

use Illuminate\Support\Facades\Concurrency;

[$user, $orders, $tickets] = Concurrency::run([
    fn () => User::with('profile')->find($id),
    fn () => Order::where('user_id', $id)->latest()->take(10)->get(),
    fn () => SupportTicket::where('user_id', $id)->open()->get(),
]);

return view('dashboard', compact('user', 'orders', 'tickets'));

The three queries run in parallel — the dashboard total time is the slowest one, not the sum. This pattern collapses three 80ms queries (240ms serial) into roughly 90ms.

5. Caching things that should never be re-resolved

// app/Providers/AppServiceProvider.php
public function boot(): void
{
    if (app()->runningInConsole()) {
        return;
    }

    // Cache the merged config tree across all workers, populated once at boot.
    Octane::tick('refresh-feature-flags', function () {
        Cache::forever('feature_flags', FeatureFlag::all()->keyBy('key'));
    })->seconds(60)->immediate();
}
💡

Pro tip

Octane ticks run in the background of every worker. Use them for expensive cache warmups, NOT for jobs that should be queued. A tick that takes 5 seconds blocks one worker for 5 seconds.

6. Real benchmarks (Forge, $40 Hetzner CX31, 4 vCPU / 8GB RAM)

Endpoint: GET /api/dashboard (auth + 4 queries)

PHP-FPM (php-fpm 8.3, 16 workers):
  Requests/sec:   712
  p50 latency:    18ms
  p95 latency:    98ms
  p99 latency:    188ms

FrankenPHP + Octane (8 workers, max-requests=1000):
  Requests/sec:   3,184
  p50 latency:    4ms
  p95 latency:    32ms
  p99 latency:    71ms

Success

These numbers are typical, not best-case

I have run these benchmarks on every project I have moved to Octane in the last two years. The 4–5× throughput and ~3× latency improvement are reproducible. The bigger your bootstrap (Filament, Nova, Spatie packages), the more you save.

7. Production checklist

1

Run `php artisan octane:status` in your healthcheck

It exposes worker count and memory usage. Alert if any worker exceeds 256MB.

2

Reload, do not restart, on deploy

`php artisan octane:reload` zero-downtime swaps workers. `octane:stop` causes a 1–2 second hiccup.

3

Move long-running work to queues

A 30-second request blocks a worker for 30 seconds. With 8 workers and 100 RPS of normal traffic, one slow request can stall everything.

4

Pin the FrankenPHP version in your Dockerfile

Major version upgrades have changed CLI flags between point releases. Lock to a tag, upgrade deliberately.

When NOT to use Octane

Note

Octane is overkill below 50 RPS

If your app handles fewer than 50 requests per second, the operational complexity is not worth the gain. Stick with PHP-FPM, focus on caching and database indexes, and revisit Octane when traffic justifies it.

For everyone else: Octane + FrankenPHP is the new default for production Laravel. Install it, audit your singletons, set --max-requests, ship it.

Newsletter

Want more posts like this?

Get practical software notes and tutorials delivered when something new is published.

No spam. Unsubscribe anytime.

How did this land?

Comments

0
Log in or sign up to join the discussion and react to this post.

No comments yet. Be the first to share your thoughts.

Related posts

Essential Sorting Algorithms for Computer Science Students

Algorithms are commonly taught in Computer Science, Software Engineering subjects at your Bachelors or Masters. Some find it difficult to understand due to memorizing.

6 years ago

GraphQL in Laravel Using Lighthouse

In modern web development, GraphQL has emerged as a powerful alternative to REST APIs due to its flexibility and efficiency.

1 year ago

Building Modern Reactive UIs with Laravel 12 and Livewire 4: A Production Guide

A production-grade walkthrough of Livewire 4 in Laravel 12 — form objects, lazy components, Alpine interop, file uploads, Pest tests, and the deployment gotchas nobody warns you about.

2 hours ago

Building Powerful Admin Panels with Laravel 12 and Filament v5: A Production Guide

Ship a real Filament v5 admin panel on Laravel 12 — Resources, RBAC with Spatie, multi-tenancy, custom widgets, and a deployment checklist for teams beyond hello-world.

3 weeks ago

Multi-Tenant SaaS with Laravel 12: A Production Architecture Guide

A practical, opinionated architecture for multi-tenant SaaS on Laravel 12 — schema, subdomain routing, tenant-aware queues, Cashier billing, and the leak tests that keep you out of the news.

1 week ago