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_requestto 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:statusto 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.
Related Articles
Why MySQL Fails to Start: A Guide to Common Errors and Solutions on Ubuntu
A practical walkthrough for diagnosing and resolving MySQL and MariaDB startup failures on Ubuntu, covering service status checks, log analysis, permission issues, and configuration repairs.
LaravelFrom Code to Production: A Guide to Automating Laravel Deployments with GitHub Actions
Learn how to build a fully automated CI/CD pipeline for Laravel using GitHub Actions, covering SSH key setup, workflow configuration, testing, and deployment to production servers.
LaravelStep-by-Step Guide to Deploying a Laravel App on AWS with Laravel Forge
A complete walkthrough of deploying a Laravel application on AWS EC2 using Laravel Forge, covering instance setup, Forge configuration, environment variables, SSL, and production best practices.