Laravel Octane in 2026: Swoole, RoadRunner and Performance Optimization

Master Laravel Octane 2.19 with FrankenPHP, Swoole and RoadRunner. Learn server selection, memory leak prevention, concurrent tasks, and production deployment patterns for high-performance PHP applications.

Laravel Octane high-performance PHP server architecture with Swoole and RoadRunner

Laravel Octane transforms PHP application performance by keeping the framework bootstrapped in memory across requests. Version 2.19 (August 2026) supports three production-ready servers: FrankenPHP, Swoole, and RoadRunner. Each serves different operational requirements, and selecting the wrong one costs either performance or stability.

Server Selection Quick Reference

FrankenPHP: simplest setup, HTTP/3 native, single binary. RoadRunner: battle-tested Go binary, no PHP extensions required. Swoole: maximum throughput with coroutine concurrency, requires PECL extension management.

How Octane Eliminates Bootstrap Overhead

Traditional PHP-FPM spawns a new process per request. Laravel boots its service container, loads configuration, registers providers, and resolves middleware on every single HTTP call. Octane inverts this model: the application boots once per worker, then the same instance handles thousands of requests sequentially.

The performance gain comes from removing repeated work. A typical Laravel 12 application spends 15 to 40ms bootstrapping before executing business logic. With Octane, that cost drops to zero after the first request. Benchmarks on identical hardware show 2x to 4x throughput improvements for API endpoints, with the gap widening as application complexity increases.

config/octane.phpphp
return [
    'server' => env('OCTANE_SERVER', 'frankenphp'),
    
    // Workers handle HTTP requests
    'workers' => env('OCTANE_WORKERS', 8),
    
    // Gracefully restart workers after N requests to prevent memory bloat
    'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
    
    // Task workers for Swoole concurrent operations
    'task_workers' => env('OCTANE_TASK_WORKERS', 6),
    
    // Maximum execution time per request in seconds
    'max_execution_time' => 30,
];

The max_requests setting acts as a safety valve. Even well-written code can accumulate memory over hundreds of requests. Setting this to 500 forces workers to restart before memory pressure becomes problematic.

FrankenPHP: The 2026 Default for New Projects

FrankenPHP ships as a single binary built on Caddy and PHP. It handles HTTPS certificates automatically through Let's Encrypt, supports HTTP/3 out of the box, and requires no PHP extensions to install. Laravel's installer and production examples now default to FrankenPHP.

bash
# Install Octane with FrankenPHP
composer require laravel/octane
php artisan octane:install --server=frankenphp

# Start the server
php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8000

For containerized deployments, FrankenPHP provides official Docker images:

dockerfile
# Dockerfile
FROM dunglas/frankenphp

RUN install-php-extensions \
    pcntl \
    pdo_pgsql \
    redis

COPY . /app

ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

The Docker Compose configuration for development enables HTTPS and HTTP/3:

yaml
# compose.yaml
services:
  frankenphp:
    build:
      context: .
    entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
    ports:
      - "443:443"
      - "443:443/udp"
    volumes:
      - .:/app

FrankenPHP handles worker restarts gracefully and integrates directly with Caddy's middleware system for advanced routing scenarios.

RoadRunner: Go-Based Stability Without Extension Dependencies

RoadRunner compiles to a Go binary that manages PHP worker processes. It communicates with PHP over a binary protocol, keeping worker management and HTTP handling in Go while executing application code in PHP. This architecture provides process isolation: a crashing PHP worker does not affect the Go parent or sibling workers.

bash
# Install Octane with RoadRunner
composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

php artisan octane:install --server=roadrunner

# Download the RoadRunner binary
./vendor/bin/rr get-binary

# Start the server
php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000

RoadRunner configuration lives in .rr.yaml at the project root:

yaml
# .rr.yaml
version: "3"

server:
  command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000"
  relay: pipes

http:
  address: 0.0.0.0:8000
  middleware: ["headers", "gzip"]
  pool:
    num_workers: 8
    max_jobs: 500
    supervisor:
      max_worker_memory: 128

The max_worker_memory setting (in MB) terminates workers that exceed memory limits, providing an additional safety net beyond max_requests. For teams with established CI pipelines and existing PHP-FPM infrastructure, RoadRunner offers the smoothest migration path.

Swoole: Maximum Throughput with Coroutine Concurrency

Swoole operates as a PHP extension that replaces the standard request lifecycle with an event-driven, coroutine-capable runtime. It provides features unavailable in other servers: concurrent task execution, ticks and intervals, and an in-memory cache with 2 million operations per second throughput.

bash
# Install Swoole via PECL
pecl install swoole

# Add to php.ini
echo "extension=swoole.so" >> $(php --ini | grep "Loaded Configuration" | cut -d: -f2 | xargs)/php.ini

# Install Octane with Swoole
composer require laravel/octane
php artisan octane:install --server=swoole

# Start with task workers for concurrent operations
php artisan octane:start --server=swoole --workers=8 --task-workers=6

Swoole's concurrent task execution handles parallel I/O operations within a single request:

app/Http/Controllers/DashboardController.phpphp
use App\Models\User;
use App\Models\Order;
use App\Models\Analytics;
use Laravel\Octane\Facades\Octane;

class DashboardController extends Controller
{
    public function index()
    {
        // Execute three queries concurrently instead of sequentially
        [$users, $orders, $analytics] = Octane::concurrently([
            fn () => User::active()->count(),
            fn () => Order::today()->sum('total'),
            fn () => Analytics::hourly()->get(),
        ]);

        return view('dashboard', compact('users', 'orders', 'analytics'));
    }
}

Without Octane::concurrently(), these three queries execute sequentially: 50ms + 30ms + 40ms = 120ms total. With concurrent execution, the total time equals the slowest query: 50ms. The --task-workers flag controls how many concurrent operations can run simultaneously across all request workers.

Swoole-Only Features

Concurrent tasks, ticks, intervals, Octane cache, and Swoole tables require the Swoole extension. FrankenPHP and RoadRunner do not support these features. Evaluate whether your application needs them before committing to Swoole's operational complexity.

Memory Leak Prevention: Writing Stateless Code

Octane's performance comes from persistent state, which creates the primary challenge: code that worked fine under PHP-FPM can leak memory catastrophically under Octane. The application instance survives across requests, so static properties, singletons, and global state accumulate.

Three patterns cause most memory leaks:

Static arrays that grow unbounded:

php
// WRONG: Memory leak - array grows with every request
class MetricsCollector
{
    public static array $data = [];

    public static function record(string $metric): void
    {
        self::$data[] = $metric; // Never cleared between requests
    }
}

// CORRECT: Use request-scoped storage or external systems
class MetricsCollector
{
    public function record(string $metric): void
    {
        Redis::lpush('metrics', $metric); // External storage
    }
}

Singletons holding request-specific data:

php
// WRONG: First request's user leaks into subsequent requests
$this->app->singleton(UserContext::class, function ($app) {
    return new UserContext($app['request']->user());
});

// CORRECT: Use closures for deferred resolution
$this->app->singleton(UserContext::class, function ($app) {
    return new UserContext(fn () => $app['request']->user());
});

Container and request injection in constructors:

php
// WRONG: Captures stale container/request
class PaymentService
{
    public function __construct(
        private Application $app,
        private Request $request
    ) {}
}

// CORRECT: Inject via method parameters or use helpers
class PaymentService
{
    public function processPayment(Request $request): void
    {
        $user = $request->user();
        $config = config('services.stripe.key'); // Global helper, always fresh
    }
}

The global helpers app(), request(), and config() always return current values. Using them instead of constructor injection sidesteps most singleton-related leaks.

Production Deployment Patterns

Octane servers need process supervision to restart on crashes. Supervisor handles this reliably:

ini
; /etc/supervisor/conf.d/octane.conf
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/octane.log
stopwaitsecs=3600

For deployments, Octane workers must reload to pick up new code. The octane:reload command handles this gracefully, waiting for in-flight requests to complete before recycling workers:

bash
# In deployment script (after git pull, composer install, etc.)
php artisan octane:reload

Nginx sits in front of Octane to serve static assets and terminate SSL:

nginx
# /etc/nginx/sites-available/app.conf
upstream octane {
    server 127.0.0.1:8000;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    root /var/www/app/public;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        try_files $uri $uri/ @octane;
    }

    location @octane {
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        proxy_pass http://octane;
    }
}

The keepalive directive maintains persistent connections between Nginx and Octane, reducing TCP handshake overhead for proxied requests.

Ready to ace your Laravel interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Server Selection Decision Tree

Choosing between FrankenPHP, RoadRunner, and Swoole depends on operational constraints and application requirements:

RequirementRecommended Server
Simplest setup, containerized deploymentFrankenPHP
HTTP/3 support out of the boxFrankenPHP
No PHP extension dependenciesFrankenPHP or RoadRunner
Maximum process isolationRoadRunner
Established PHP-FPM infrastructureRoadRunner
Concurrent I/O within requestsSwoole
In-memory caching (2M ops/sec)Swoole
Ticks, intervals, background tasksSwoole
Swoole tables for shared stateSwoole

For teams starting new projects in 2026, FrankenPHP provides the best balance of simplicity and capability. Migrating existing applications works best with RoadRunner due to its process isolation and familiar deployment model. Swoole suits high-throughput APIs that benefit from coroutine concurrency and require Swoole-specific features.

Interview Questions on Laravel Octane

Technical interviews increasingly cover Octane as high-traffic Laravel applications adopt it. Common questions and what interviewers look for:

"How does Octane achieve its performance gains?"

The expected answer: Octane boots Laravel once per worker and reuses that instance across requests, eliminating the 15-40ms bootstrap cost on each request. Candidates should mention that service providers' register and boot methods run once per worker, not per request.

"What causes memory leaks in Octane applications?"

Strong answers identify three sources: static arrays that accumulate data, singletons capturing request-specific state, and constructor injection of the container or request objects. Candidates should explain that the fix involves using closures for deferred resolution or passing data through method parameters.

"When would you choose Swoole over RoadRunner?"

Swoole's advantages are coroutine concurrency via Octane::concurrently(), the high-speed Octane cache, and features like ticks and intervals. The tradeoff is operational complexity: Swoole requires managing a PECL extension, while RoadRunner ships as a Go binary. For CRUD applications without concurrent I/O needs, RoadRunner is simpler to operate. For more Laravel interview preparation, see PHP Laravel Developer Interview Questions 2026.

"How do you handle deployments with Octane?"

The deployment sequence: stop accepting new connections, wait for in-flight requests to complete, reload workers with new code. The php artisan octane:reload command handles this. Candidates should mention Supervisor or systemd for process supervision and Nginx or Caddy as a reverse proxy.

Monitoring and Profiling Octane Applications

Memory monitoring becomes critical under Octane. Workers persist, so memory growth indicates leaks rather than normal request overhead. Laravel Pulse and Telescope both work with Octane and provide request-level profiling.

app/Providers/AppServiceProvider.phpphp
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\RequestTerminated;

public function boot(): void
{
    Event::listen(RequestReceived::class, function ($event) {
        $event->sandbox->instance('request.memory.start', memory_get_usage());
    });

    Event::listen(RequestTerminated::class, function ($event) {
        $start = $event->sandbox->make('request.memory.start');
        $delta = memory_get_usage() - $start;
        
        if ($delta > 1024 * 1024) { // More than 1MB growth
            Log::warning('High memory delta', [
                'path' => $event->request->path(),
                'delta_mb' => round($delta / 1024 / 1024, 2),
            ]);
        }
    });
}

This event listener logs requests that increase memory by more than 1MB, helping identify endpoints that need optimization. For production monitoring, external APM tools like Datadog or New Relic provide worker-level visibility.

Integrating Octane with Queues and Horizon

Octane handles HTTP requests; Laravel Horizon manages queue workers. The two operate independently and complement each other. Heavy processing belongs in queued jobs, keeping Octane workers responsive.

app/Http/Controllers/ReportController.phpphp
use App\Jobs\GenerateReport;

class ReportController extends Controller
{
    public function generate(Request $request)
    {
        // Dispatch to queue instead of blocking the Octane worker
        GenerateReport::dispatch($request->user(), $request->input('parameters'));

        return response()->json(['status' => 'processing']);
    }
}

For related patterns on background processing, see Laravel Queues and Jobs: Asynchronous Architecture and Laravel Middleware Deep Dive.

What to Remember About Laravel Octane in 2026

  • Octane 2.19 supports three servers: FrankenPHP (simplest), RoadRunner (most stable), and Swoole (most features)
  • Performance gains come from eliminating per-request bootstrap; expect 2x to 4x throughput improvement
  • Memory leaks occur because worker state persists; avoid static arrays, constructor-injected requests, and singletons with request data
  • Use --max-requests=500 to force worker recycling before memory pressure builds
  • Swoole-only features include Octane::concurrently(), ticks, intervals, and the Octane cache
  • Deploy behind Nginx or Caddy with Supervisor for process management
  • Profile memory growth per request; deltas over 1MB indicate potential leaks
  • Queue heavy work through Horizon; keep Octane workers handling fast HTTP responses

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Daily challenge

Can you spot the bug in Laravel?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on September 11, 2026

Tags

#laravel
#octane
#swoole
#roadrunner
#frankenphp
#performance
#php

Share

Related articles