Laravel Octane supercharges PHP by loading your application once and keeping it in memory — eliminating the per-request bootstrap overhead of traditional PHP-FPM. With the Swoole driver on a Hong Kong VPS, your Laravel application handles thousands of concurrent requests with sub-10ms response times, bringing PHP performance into territory normally reserved for Go or Rust — while retaining Laravel’s excellent developer experience and ecosystem.
How Octane Differs from PHP-FPM
| Aspect | Traditional PHP-FPM | Laravel Octane (Swoole) |
|---|---|---|
| App bootstrap per request | Yes — full boot each time | No — boots once, stays in memory |
| Framework loading time | 20–80ms per request | ~0ms (already loaded) |
| Concurrency model | Process-per-request (pm.max_children) | Event loop — thousands concurrent |
| Memory per worker | 30–60MB per process | 40–80MB per worker (serves all requests) |
| Typical TTFB (simple route) | 30–80ms | 2–8ms |
| WebSocket support | No (requires separate server) | Yes (built into Swoole) |
| Memory leak risk | None (process dies per request) | Must audit for memory leaks in services |
Step 1: Install Swoole Extension
<code">apt update add-apt-repository ppa:ondrej/php -y && apt update apt install -y php8.3-cli php8.3-dev php8.3-pcov \ libssl-dev gcc make autoconf libc-dev pkg-config # Install Swoole via PECL pecl install swoole echo "extension=swoole.so" > /etc/php/8.3/mods-available/swoole.ini phpenmod swoole # Verify php -m | grep swoole # Should show: swoole
Step 2: Install Laravel Octane
<code">cd /var/www/mylaravel composer require laravel/octane php artisan octane:install --server=swoole # Test locally php artisan octane:start --server=swoole --host=127.0.0.1 --port=8000 --workers=4 # In another terminal — check it works curl http://127.0.0.1:8000/health
Step 3: Configure Octane
<code"># config/octane.php — key production settings
return [
'server' => env('OCTANE_SERVER', 'swoole'),
'swoole' => [
'options' => [
'log_level' => SWOOLE_LOG_INFO,
'worker_num' => env('OCTANE_WORKERS', max(1, (int)shell_exec('nproc'))),
'task_worker_num' => env('OCTANE_TASK_WORKERS', 6),
'max_request' => env('OCTANE_MAX_REQUESTS', 500), // Restart after 500 requests (prevents memory leaks)
'enable_coroutine' => true,
'http_compression' => true,
'http_compression_level' => 6,
],
],
'listeners' => [
// Clean up state between requests:
WorkerStarting::class => [
EnsureUploadedFilesAreValid::class,
],
RequestReceived::class => [
EnsureUploadedFilesAreValid::class,
],
RequestHandled::class => [
FlushTemporaryContainerInstances::class,
],
RequestTerminated::class => [
// Flush bindings that accumulate state
],
],
'warm' => [
// Services to pre-warm on worker start
...Octane::defaultServicesToWarm(),
],
];
Environment Variables
<code"># In .env: OCTANE_SERVER=swoole OCTANE_WORKERS=4 # Match vCPU count OCTANE_TASK_WORKERS=6 OCTANE_MAX_REQUESTS=500
Step 4: Systemd Service for Octane
<code">cat > /etc/systemd/system/laravel-octane.service << 'EOF'
[Unit]
Description=Laravel Octane (Swoole)
After=network.target mysql.service redis.service
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/mylaravel
ExecStart=/usr/bin/php artisan octane:start \
--server=swoole \
--host=127.0.0.1 \
--port=8000 \
--workers=4 \
--task-workers=6 \
--max-requests=500
ExecReload=/usr/bin/php artisan octane:reload
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now laravel-octane
systemctl status laravel-octaneStep 5: Nginx Configuration
<code">cat > /etc/nginx/sites-available/octane << 'EOF'
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream octane {
server 127.0.0.1:8000;
keepalive 16;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
root /var/www/mylaravel/public;
# Serve static files directly from Nginx (bypass Octane)
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2|ttf|pdf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ @octane;
}
location @octane {
proxy_pass http://octane;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
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_read_timeout 60s;
}
}
EOF
ln -s /etc/nginx/sites-available/octane /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginxStep 6: Memory Leak Prevention
Because Octane keeps your application in memory between requests, singletons and static state that traditional PHP discards per-request now persist. This causes memory leaks if not managed correctly.
<code"># Common sources of memory leaks in Octane:
# 1. Static properties that accumulate data
# 2. Singletons that cache request-specific data
# 3. Event listeners that aren't cleaned up
# In your service providers — never store request data in singletons:
// BAD: accumulates every request
class UserCache {
private static array $cache = [];
public static function remember(int $id): User {
self::$cache[$id] ??= User::find($id); // Grows forever!
return self::$cache[$id];
}
}
// GOOD: use Redis or Laravel's cache (properly scoped)
class UserCache {
public function remember(int $id): User {
return Cache::remember("user.$id", 300, fn() => User::find($id));
}
}<code"># Monitor Octane worker memory usage php artisan octane:status # Output shows: # Workers: 4 (PID: 1234, 1235, 1236, 1237) # Memory per worker: 48MB, 49MB, 48MB, 47MB # If memory grows continuously: you have a leak — use max_requests=500 # to auto-restart workers before they get too large
Test for Memory Leaks
<code"># Siege load test to identify memory growth apt install -y siege siege -c 50 -t 60S https://yourdomain.com/api/heavy-endpoint # Monitor worker memory during the test: watch -n 2 "php artisan octane:status" # Healthy: memory stays roughly constant # Leaking: memory grows continuously across requests
Step 7: WebSockets with Octane + Laravel Echo
<code"># Octane + Swoole supports WebSockets natively
# Install Laravel WebSockets package:
composer require beyondcode/laravel-websockets
# In config/broadcasting.php — set driver to pusher (compatible with Laravel Echo)
# In config/websockets.php — configure the WebSocket server
# Start WebSocket server via Octane (runs on same process):
php artisan octane:start --server=swoole --host=0.0.0.0 --port=8000
# Client-side Laravel Echo configuration:
import Echo from 'laravel-echo';
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
wsHost: 'yourdomain.com',
wsPort: 443,
forceTLS: true,
encrypted: true,
});Octane + Redis Caching = Maximum Performance
<code"># With Octane keeping the app in memory + Redis for caching,
# cached route responses serve in 2-5ms from China via CN2 GIA:
// In a controller — responses cached in Redis, served in-memory:
public function products(): JsonResponse {
$products = Cache::remember('products.active', 300, function() {
return Product::where('active', true)
->with('categories')
->get();
});
return response()->json($products);
}
// First request: DB query (~20ms) + Cache store
// Subsequent requests: Cache hit (~2ms) + no DB query
// TTFB from China: 2ms cache retrieval + 30-40ms CN2 GIA network = ~35ms totalDeployment: Zero-Downtime Reload
<code"># The key Octane advantage: reload without dropping connections cd /var/www/mylaravel git pull origin main composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan config:cache php artisan route:cache php artisan view:cache # Graceful reload — workers complete current requests, then restart with new code php artisan octane:reload # All workers updated with zero dropped requests
Conclusion
Laravel Octane with Swoole on a Hong Kong VPS transforms PHP from a per-request scripting language into a persistent, high-concurrency application server — delivering 2–8ms TTFB for cached responses versus 30–80ms for traditional PHP-FPM. Combined with CN2 GIA routing, cached API responses from your Hong Kong VPS reach mainland Chinese clients in 35–55ms total — competitive with any cloud-native API service at a fraction of the cost. The max_requests setting prevents memory leaks without developer intervention, making Octane production-safe for teams without deep Swoole expertise.
Run high-performance PHP: Browse Server.HK Hong Kong VPS plans — a 4 GB plan with 4 vCPU runs Octane with 4 Swoole workers serving thousands of concurrent requests.