Laravel Octane 2026 — 400% Performance, Real Setup Guide
I have been running Laravel applications on PHP-FPM for years. It works. It is predictable. Every developer on the team understands it. Debugging is straightforward. Deployments are boring in the good way.
Then I looked at what Octane actually does — not the marketing version, but the architectural reality — and understood why teams are reporting numbers that sound too good to be true.
A SaaS platform migrated from traditional Laravel deployment to Octane achieved a 400% improvement in request throughput while reducing server costs by 60%. Itpathsolutions
That is not a benchmark in ideal lab conditions. That is a production migration on a real application. This guide covers exactly what changed, how to set it up properly in 2026, and the gotchas that will bite you if you go in unprepared.
Why PHP-FPM Has a Hidden Performance Problem
To understand what Octane does, you need to understand what PHP-FPM does on every single request.
Traditional PHP-FPM boots the entire Laravel framework from scratch on every single request — loading configuration, registering service providers, resolving bindings — before handling the request itself. Octane boots the application once and keeps it in memory, serving subsequent requests from that warm state. GitHub
Here is what that cycle looks like on every request with PHP-FPM:
Request arrives → PHP process starts → Load composer autoloader → Boot Laravel application → Register all service providers → Resolve container bindings → Boot middleware stack → Handle the actual request → Send response → PHP process dies → Everything above repeats on next request
For a typical Laravel application, that bootstrap cycle takes 20-80ms before your code runs a single line of actual business logic. On a high-traffic API serving 1,000 requests per second, you are paying that bootstrap tax one thousand times per second.
Laravel Octane keeps your app booted in memory to reduce bootstrapping overhead, supports multiple high-performance drivers, and can deliver large throughput gains — but it changes the execution model, so you must manage state and memory carefully. Laravel
That last part is the part most tutorials skip. We will cover it properly.
The Three Drivers — Which One to Choose
Laravel Octane supercharges your application's performance by serving your application using high-powered application servers, including FrankenPHP, Open Swoole, Swoole, and RoadRunner. Octane boots your application once, keeps it in memory, and then feeds it requests at supersonic speeds. Boundev
In 2026, the practical choice is between three:
FrankenPHP — Best Starting Point for Most Teams
FrankenPHP is a PHP application server, written in Go, that supports modern web features like early hints, Brotli, and Zstandard compression. Boundev
Choose FrankenPHP if:
- You are setting up Octane for the first time
- You want the simplest production deployment
- You need modern web features like HTTP/3 and early hints
- Your team is not comfortable compiling PHP extensions
bash
composer require laravel/octane php artisan octane:install --server=frankenphp php artisan octane:start --server=frankenphp --port=8000
FrankenPHP ships as a single binary. No extension installation. No compilation. That simplicity is worth a lot in production.
RoadRunner — Balanced Choice for APIs
RoadRunner is easier to set up and has better Windows support. Swoole generally offers better raw performance — 10-15% faster — but has more complex installation. OneUptime
Choose RoadRunner if:
- You want easier setup than Swoole with better performance than PHP-FPM
- Your team develops on Windows
- You want the Go-based plugin ecosystem
- You need predictable resource usage
bash
composer require laravel/octane php artisan octane:install --server=roadrunner php artisan octane:start --server=roadrunner --port=8000
Swoole — Maximum Performance for High Traffic
Choose Swoole if:
- You have genuinely high traffic (10k+ requests per minute)
- You need concurrent tasks, coroutines, or async I/O
- You are comfortable installing PHP extensions
- Raw performance is the primary concern
bash
# Install Swoole extension first pecl install swoole # Add to php.ini extension=swoole # Then install Octane composer require laravel/octane php artisan octane:install --server=swoole php artisan octane:start --server=swoole --port=8000 --workers=4
Complete Installation — Step by Step
Step 1 — Install Octane
bash
composer require laravel/octane php artisan octane:install
The installer prompts you to choose your server. Select based on the guide above.
Step 2 — Configure octane.php
The installer creates config/octane.php. These are the settings that actually matter:
php
// config/octane.php
return [
'server' => env('OCTANE_SERVER', 'frankenphp'),
// Worker count — most impactful setting
// Rule of thumb: (CPU cores * 2) for API apps
// (CPU cores) for memory-intensive apps
'workers' => env('OCTANE_WORKERS', 4),
// Task workers for concurrent background tasks (Swoole only)
'task_workers' => env('OCTANE_TASK_WORKERS', 2),
// Restart worker after N requests to prevent memory leaks
// Lower for memory-heavy apps, higher for light apps
'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
// Max execution time per request in seconds
'max_execution_time' => 30,
// Listeners that flush state between requests
'listeners' => [
WorkerStarting::class => [
EnsureUploadedFilesAreValid::class,
EnsureRequestServerPortMatchesScheme::class,
],
RequestReceived::class => [
...Octane::prepareApplicationForNextRequest(),
],
RequestHandled::class => [
FlushUploadedFiles::class,
],
RequestTerminated::class => [
FlushLocaleState::class,
FlushQueuedCookies::class,
FlushSessionState::class,
FlushAuthenticationState::class,
FlushCryptographyRequestState::class,
],
],
// Services to warm on worker start
'warm' => [
...Octane::defaultServicesToWarm(),
],
];
Step 3 — Environment Variables
env
OCTANE_SERVER=frankenphp OCTANE_WORKERS=4 OCTANE_MAX_REQUESTS=500 APP_ENV=production APP_DEBUG=false
Step 4 — Start and Verify
bash
# Development php artisan octane:start --watch # Production php artisan octane:start --workers=4 --max-requests=500 # Check status php artisan octane:status # Reload workers without downtime php artisan octane:reload # Stop php artisan octane:stop
The Worker Count Calculation
The most impactful configuration parameter is worker count. OneUptime
Getting this wrong is the single biggest cause of suboptimal Octane performance.
bash
# Check your CPU cores nproc # Linux sysctl -n hw.ncpu # macOS
General rules:
- API-heavy apps (mostly I/O):
CPU cores × 2 - CPU-heavy apps (heavy computation):
CPU cores - Mixed apps:
CPU cores × 1.5, round up
env
# 4-core server, API-heavy OCTANE_WORKERS=8 # 4-core server, CPU-heavy OCTANE_WORKERS=4 # 4-core server, mixed OCTANE_WORKERS=6
Monitor memory per worker and adjust. Each worker holds your entire application in memory.
Performance Numbers — What to Expect
Most applications see 5-20x performance improvements in requests per second and reduced latency. API applications typically see the highest gains — 15-20x — while view-heavy applications see 5-10x improvements. The exact improvement depends on your specific application architecture and workload patterns. OneUptime
Benchmark your own application before and after migration:
bash
# Install Apache Bench (comes with Apache tools) # Before Octane — PHP-FPM ab -n 1000 -c 50 http://yourdomain.com/api/v1/health # After Octane ab -n 1000 -c 50 http://yourdomain.com/api/v1/health
Or use autocannon for a more modern benchmark:
bash
npx autocannon -c 100 -d 10 http://localhost:8000/api/v1/health
Compare: requests per second, mean latency, p99 latency.
The Gotchas — What Nobody Warns You About
This section is the most important part of the guide. Octane changes the PHP execution model in ways that break assumptions every Laravel developer has built up over years.
Gotcha 1 — Memory Does Not Clear Between Requests
In PHP-FPM, every request gets a clean process. Memory is cleared automatically. In Octane, the same worker handles thousands of requests without dying.
What this means: Any state you accidentally store globally persists across requests.
php
// DANGEROUS in Octane — this accumulates across requests
class ReportService
{
private static array $cache = [];
public function generate(int $id): array
{
if (!isset(static::$cache[$id])) {
static::$cache[$id] = $this->buildReport($id);
}
return static::$cache[$id];
}
}
// After 500 requests, static::$cache is enormous
// Worker runs out of memory and dies
php
// SAFE — per-request cache via the container
class ReportService
{
private array $cache = []; // instance variable, not static
public function generate(int $id): array
{
if (!isset($this->cache[$id])) {
$this->cache[$id] = $this->buildReport($id);
}
return $this->cache[$id];
}
}
// Register as scoped (new instance per request)
$this->app->scoped(ReportService::class);
Gotcha 2 — Singleton Leaks Between Requests
php
// DANGEROUS — singleton retains user state across requests
class AuthContext
{
private ?User $user = null;
public function setUser(User $user): void
{
$this->user = $user;
}
public function getUser(): ?User
{
return $this->user; // Returns previous request's user!
}
}
// Registered as singleton — same instance across all requests
app()->singleton(AuthContext::class);
php
// SAFE — scoped binding gives fresh instance per request
app()->scoped(AuthContext::class);
// Or register a flush callback in octane.php listeners
RequestTerminated::class => [
function () {
app(AuthContext::class)->flush();
},
],
Gotcha 3 — Database Connection Pooling Behavior Changes
php
// config/database.php — add for Octane
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
// ...
// Octane keeps connections alive between requests
// Set pool size to match your worker count
'pool' => [
'min_connections' => 1,
'max_connections' => env('OCTANE_WORKERS', 4),
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60.0,
],
],
Gotcha 4 — Third-Party Package Compatibility
Not every package was written with persistent memory in mind. Before migrating to production, audit your key packages:
bash
# Test for memory leaks with wrk wrk -t4 -c100 -d30s http://localhost:8000/api/v1/your-endpoint # Monitor memory during test watch -n 1 'php artisan octane:status'
Known packages with Octane issues (check each package's current status):
- Packages that store state in static properties
- Packages that use
register_shutdown_function() - Debugbar in non-development environments
- Some older Spatie packages (most have been updated)
Gotcha 5 — File Uploads Need Special Handling
php
// Octane automatically handles this via FlushUploadedFiles listener
// But verify your upload handling is clean
// SAFE — Octane's default listeners clean this up
public function store(Request $request): JsonResponse
{
$file = $request->file('avatar'); // Safe — cleaned after request
$path = $file->store('avatars', 's3');
return response()->json(['path' => $path]);
}
Production Deployment — Nginx + Systemd
Nginx Configuration
nginx
# /etc/nginx/sites-available/laravel-octane
server {
listen 80;
server_name yourdomain.com;
# Redirect all HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Proxy to Octane
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Systemd Service
ini
# /etc/systemd/system/laravel-octane.service
[Unit]
Description=Laravel Octane Server
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/yourapp
ExecStart=/usr/bin/php artisan octane:start \
--server=frankenphp \
--host=127.0.0.1 \
--port=8000 \
--workers=4 \
--max-requests=500
Restart=always
RestartSec=5
# Environment
Environment=APP_ENV=production
Environment=APP_DEBUG=false
# Logging
StandardOutput=append:/var/log/octane.log
StandardError=append:/var/log/octane-error.log
[Install]
WantedBy=multi-user.target
bash
# Enable and start sudo systemctl enable laravel-octane sudo systemctl start laravel-octane sudo systemctl status laravel-octane # Zero-downtime reload sudo systemctl reload laravel-octane # or php artisan octane:reload
Zero-Downtime Deployment Script
bash
#!/bin/bash # deploy.sh set -e echo "Pulling latest code..." git pull origin main echo "Installing dependencies..." composer install --no-dev --optimize-autoloader echo "Running migrations..." php artisan migrate --force echo "Clearing caches..." php artisan config:cache php artisan route:cache php artisan view:cache php artisan event:cache echo "Reloading Octane workers (zero downtime)..." php artisan octane:reload echo "Deployment complete."
Concurrent Tasks with Swoole — Octane's Hidden Power
If you are running Swoole, Octane unlocks concurrent task execution — running multiple operations genuinely in parallel within a single request.
php
use Laravel\Octane\Facades\Octane;
public function dashboard(Request $request): JsonResponse
{
// These three queries run CONCURRENTLY — not sequentially
[$stats, $recentOrders, $topProducts] = Octane::concurrently([
fn () => $this->analyticsService->getDashboardStats(),
fn () => Order::with('customer')
->latest()
->limit(10)
->get(),
fn () => Product::withCount('orders')
->orderByDesc('orders_count')
->limit(5)
->get(),
]);
return response()->json([
'stats' => $stats,
'orders' => OrderResource::collection($recentOrders),
'top_products' => ProductResource::collection($topProducts),
]);
}
Instead of three sequential queries (say 150ms each = 450ms total), all three run in parallel — total time is the slowest of the three, roughly 150ms. For data-heavy dashboards, this is the biggest single performance win Octane provides.
Is Octane Right for Your Application?
Octane is the right choice if:
- Your application handles significant traffic (500+ requests per minute)
- You have a Laravel API serving a mobile app or SPA
- Response time is a user-visible concern
- You are willing to audit for memory safety issues
Stick with PHP-FPM if:
- Your application has low to moderate traffic
- You rely on packages with known Octane compatibility issues
- Your team is not familiar with long-running process patterns
- Simplicity and debuggability are the priority
The honest middle ground: Run PHP-FPM in production and benchmark. If response times are acceptable and servers are not a bottleneck, Octane adds complexity for gains you may not need. If you are scaling horizontally by adding servers because a single server cannot handle the load — Octane is the better investment.
Production Checklist
Before enabling Octane:
- Audit all static properties in your codebase
- Check singleton bindings for request-scoped state
- Verify all third-party packages are Octane-compatible
- Test file upload handling
- Set
max_requeststo prevent gradual memory growth
Configuration:
- Worker count set to
CPU cores × 2for API apps -
max_requestsset (500 is a safe default, tune per app) - Database connection pool matches worker count
- Redis connection pool configured if using Redis
Production deployment:
- Nginx reverse proxy configured
- Systemd service with
Restart=always - Zero-downtime reload script ready
- Memory monitoring in place
-
octane:statusaccessible for health checks
Wrapping Up
Laravel Octane is not a drop-in replacement for PHP-FPM that you enable and forget. It is an architectural change that requires understanding the new execution model — specifically that memory persists across requests and state must be managed deliberately.
But for applications with real traffic, the performance numbers are not exaggerated. API applications typically see 15-20x improvements in requests per second. That is the difference between needing four servers and needing one. OneUptime
Audit your codebase for memory safety issues, choose the right driver for your team and use case, set worker count correctly, and monitor memory in the first week. After that it is largely set-and-forget.
The 400% throughput claim is real. So is the work required to get there cleanly.
Tushar Modi — Full Stack Developer, Jaipur tusharmodi.in