Skip to main content
Back to Articles
LaravelApril 28, 20258 min read

Laravel Octane Performance Optimization Guide

Boost Laravel throughput 10x with Octane using Swoole and RoadRunner, connection pooling, memory management, and production deployment strategies.

What Octane Changes

Traditional Laravel runs on PHP-FPM, where every request bootstraps the entire framework — service providers, configuration, routes, and middleware. This boot process takes 20-50ms before your application code even executes. Laravel Octane eliminates this overhead by keeping the application in memory between requests using high-performance servers like Swoole or RoadRunner.

The result is dramatic: response times drop from 80-200ms to 5-20ms, and throughput increases 5-10x on the same hardware. This guide covers production-grade Octane deployment based on our server optimization experience.

Choosing Between Swoole and RoadRunner

Swoole

Swoole is a PHP extension written in C that provides an event-driven, coroutine-based runtime. It handles HTTP serving, WebSockets, and task workers natively.

Advantages: Highest raw performance, built-in coroutines for concurrent I/O, WebSocket support, task workers for background processing.

Disadvantages: Requires PHP extension installation (not available on all hosting), less compatible with some PHP libraries that use global state.

RoadRunner

RoadRunner is a Go-based application server that communicates with PHP workers via pipes. It manages worker pools and handles HTTP serving externally.

Advantages: No PHP extension required, simpler deployment, better compatibility with existing PHP libraries, built-in metrics endpoint.

Disadvantages: Slightly lower performance than Swoole (still 5-8x faster than PHP-FPM), no native coroutine support.

Installation

# Swoole
pecl install swoole
composer require laravel/octane
php artisan octane:install --server=swoole

# RoadRunner
composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
php artisan octane:install --server=roadrunner
./vendor/bin/rr get-binary

Production Configuration

Swoole Configuration

// config/octane.php
'swoole' => [
    'options' => [
        'worker_num' => env('OCTANE_WORKERS', 8),
        'task_worker_num' => env('OCTANE_TASK_WORKERS', 4),
        'max_request' => 1000,
        'enable_static_handler' => false,
        'package_max_length' => 10 * 1024 * 1024,
        'buffer_output_size' => 10 * 1024 * 1024,
        'socket_buffer_size' => 128 * 1024 * 1024,
    ],
],

Setting max_request: 1000 restarts workers after 1,000 requests to prevent memory leaks. Set worker_num to match your CPU core count for optimal performance.

RoadRunner Configuration

# .rr.yaml
version: "3"

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

http:
  address: "0.0.0.0:8000"
  pool:
    num_workers: 8
    max_jobs: 1000
    allocate_timeout: 60s
    destroy_timeout: 60s
    supervisor:
      max_worker_memory: 256
      ttl: 3600

logs:
  mode: production
  level: warn
  output: stderr

Memory Leak Prevention

The biggest challenge with Octane is memory management. Since the application persists between requests, any state stored in static properties, singletons, or service container bindings accumulates across requests.

Common Memory Leak Sources

// BAD: Static property accumulates data across requests
class AnalyticsService
{
    private static array $events = [];

    public function track(string $event): void
    {
        static::$events[] = $event; // Grows forever
    }
}

// GOOD: Use request-scoped state
class AnalyticsService
{
    private array $events = [];

    public function track(string $event): void
    {
        $this->events[] = $event; // Reset each request when resolved fresh
    }
}

Octane Listeners for Cleanup

// app/Providers/OctaneServiceProvider.php
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\RequestTerminated;

public function boot(): void
{
    $this->app['events']->listen(RequestTerminated::class, function () {
        // Reset any request-scoped state
        app(CartService::class)->reset();
        app(AuthContext::class)->clear();
    });
}

Database Connection Pooling

With PHP-FPM, each request opens and closes a database connection. Octane workers maintain persistent connections, but you need to handle disconnections gracefully:

// config/octane.php
'listeners' => [
    RequestReceived::class => [
        EnsureDatabaseConnectionIsActive::class,
    ],
],

// Custom listener
class EnsureDatabaseConnectionIsActive
{
    public function handle(RequestReceived $event): void
    {
        $connection = $event->app->make('db')->connection();
        try {
            $connection->getPdo()->getAttribute(PDO::ATTR_CONNECTION_STATUS);
        } catch (\Exception) {
            $connection->reconnect();
        }
    }
}

Concurrent Tasks with Swoole

Octane with Swoole enables concurrent I/O operations — a game-changer for endpoints that call multiple external services:

use Laravel\Octane\Facades\Octane;

public function getDashboardData(): array
{
    [$users, $orders, $metrics] = Octane::concurrently([
        fn () => $this->userService->getActiveCount(),
        fn () => $this->orderService->getRecentOrders(50),
        fn () => $this->metricsService->getDailyMetrics(),
    ]);

    return compact('users', 'orders', 'metrics');
}

Three database queries or API calls that normally take 300ms sequentially complete in 100ms when run concurrently.

Deployment with Supervisor

# /etc/supervisor/conf.d/octane.conf
[program:octane]
command=php /var/www/app/artisan octane:start --server=swoole --host=0.0.0.0 --port=8000 --workers=8 --task-workers=4
user=www-data
autostart=true
autorestart=true
stopwaitsecs=30
redirect_stderr=true
stdout_logfile=/var/log/octane/octane.log
stopasgroup=true
killasgroup=true

Zero-Downtime Deployment Script

#!/bin/bash
cd /var/www/app
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan octane:reload

The octane:reload command gracefully restarts workers without dropping active connections.

Benchmarking Results

Typical performance comparison on a 4-core server:

| Metric | PHP-FPM | Octane (RoadRunner) | Octane (Swoole) | |--------|---------|--------------------|-----------------| | Requests/sec | 250 | 1,800 | 2,400 | | P50 latency | 45ms | 8ms | 5ms | | P99 latency | 180ms | 25ms | 18ms | | Memory per worker | 30MB | 45MB | 50MB |

Best Practices

  • Start with RoadRunner for easier migration, switch to Swoole when you need maximum performance
  • Set max_request to restart workers periodically and prevent memory accumulation
  • Audit all service providers and singletons for request-scoped state before deploying Octane
  • Monitor memory usage per worker with Prometheus or your infrastructure monitoring stack
  • Use php artisan octane:status to verify worker health
  • Run Octane behind NGINX for SSL termination and static file serving

Octane transforms Laravel from a request-per-process model to a persistent application server, delivering performance that rivals Go and Node.js while keeping the developer experience Laravel is known for.

Need help with this?

Our team handles this kind of work daily. Let us take care of your infrastructure.