Skip to content

Performance Tuning Guide

Performance Tuning Guide

Comprehensive guide to optimizing Cbox containers for maximum performance in production environments.

Table of Contents

Performance Benchmarking

Baseline Measurement

Always measure before optimizing:

# Install Apache Bench
apt-get install apache2-utils

# Simple benchmark (1000 requests, 10 concurrent)
ab -n 1000 -c 10 http://localhost/

# With keep-alive
ab -n 1000 -c 10 -k http://localhost/

# POST request benchmark
ab -n 1000 -c 10 -p data.json -T application/json http://localhost/api/endpoint

Load Testing with K6

// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },   // Ramp up
    { duration: '3m', target: 50 },   // Stay at 50 users
    { duration: '1m', target: 100 },  // Ramp to 100
    { duration: '3m', target: 100 },  // Stay at 100
    { duration: '1m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% under 500ms
    http_req_failed: ['rate<0.01'],    // Less than 1% failed
  },
};

export default function () {
  const res = http.get('http://localhost:8000');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);
}
# Run load test
k6 run load-test.js

PHP-FPM Optimization

Process Manager Selection

Dynamic (Default - Best for Variable Traffic):

services:
  app:
    environment:
      - PHP_FPM_PM=dynamic
      - PHP_FPM_PM_MAX_CHILDREN=50
      - PHP_FPM_PM_START_SERVERS=10
      - PHP_FPM_PM_MIN_SPARE_SERVERS=5
      - PHP_FPM_PM_MAX_SPARE_SERVERS=15
      - PHP_FPM_PM_MAX_REQUESTS=1000

Static (Best for Consistent High Traffic):

services:
  app:
    environment:
      - PHP_FPM_PM=static
      - PHP_FPM_PM_MAX_CHILDREN=50

OnDemand (Best for Low Traffic / Memory Constrained):

services:
  app:
    environment:
      - PHP_FPM_PM=ondemand
      - PHP_FPM_PM_MAX_CHILDREN=20
      - PHP_FPM_PM_PROCESS_IDLE_TIMEOUT=10s

Calculating Optimal max_children

Formula:

max_children = Available RAM / Average Process Memory

Measure process memory:

# Average PHP-FPM process memory
docker exec <container> sh -c 'ps aux | grep "php-fpm: pool" | awk "{sum+=\$6} END {print sum/NR/1024 \"MB\"}"'

# Example output: 45MB per process

# If you have 2GB for PHP-FPM:
# max_children = 2048MB / 45MB = ~45 processes

Verification:

# Monitor actual usage
docker stats <container>

# Check PHP-FPM status
curl http://localhost/fpm-status

# Watch process count
watch -n 1 'docker exec <container> ps aux | grep php-fpm | wc -l'

Process Manager Tuning by Workload

API Server (Stateless, Fast Requests):

pm = static
pm.max_children = 100
pm.max_requests = 500
request_terminate_timeout = 10s

Web Application (Mixed Workload):

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 1000
request_terminate_timeout = 30s

Background Jobs (Long-Running Tasks):

pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 30s
request_terminate_timeout = 300s

Request Lifecycle Optimization

; Prevent memory leaks by recycling processes
pm.max_requests = 1000

; Terminate long-running requests
request_terminate_timeout = 60s

; Log slow requests
request_slowlog_timeout = 5s
slowlog = /proc/self/fd/2

; Increase buffer sizes for large requests
php_admin_value[post_max_size] = 100M
php_admin_value[upload_max_filesize] = 100M

OPcache Tuning

Production Configuration

[opcache]
; Enable OPcache
opcache.enable = 1
opcache.enable_cli = 0

; Memory allocation
opcache.memory_consumption = 256        ; Increase for large codebases
opcache.interned_strings_buffer = 16   ; Increase for many classes
opcache.max_accelerated_files = 20000  ; More than total PHP files

; Validation (disable in production for speed)
opcache.validate_timestamps = 0        ; Don't check file changes
opcache.revalidate_freq = 0            ; Not used when validation disabled

; Performance optimizations
opcache.enable_file_override = 1       ; Faster file_exists checks
opcache.save_comments = 1              ; Required for some frameworks
opcache.fast_shutdown = 1              ; Faster shutdown

; Optimization level
opcache.optimization_level = 0x7FFEBFFF  ; Maximum optimization

; JIT (PHP 8.0+)
opcache.jit = 1255                     ; Enable JIT compilation
opcache.jit_buffer_size = 100M         ; JIT buffer size

Via environment variables:

services:
  app:
    environment:
      # OPcache Production
      - PHP_OPCACHE_ENABLE=1
      - PHP_OPCACHE_VALIDATE_TIMESTAMPS=0
      - PHP_OPCACHE_MEMORY_CONSUMPTION=256
      - PHP_OPCACHE_INTERNED_STRINGS_BUFFER=16
      - PHP_OPCACHE_MAX_ACCELERATED_FILES=20000

OPcache Monitoring

<?php
// opcache-status.php
$status = opcache_get_status();
$config = opcache_get_configuration();

header('Content-Type: application/json');
echo json_encode([
    'enabled' => $status !== false,
    'memory_usage' => [
        'used_memory' => $status['memory_usage']['used_memory'],
        'free_memory' => $status['memory_usage']['free_memory'],
        'wasted_memory' => $status['memory_usage']['wasted_memory'],
        'current_wasted_percentage' => $status['memory_usage']['current_wasted_percentage'],
    ],
    'opcache_statistics' => [
        'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'],
        'num_cached_keys' => $status['opcache_statistics']['num_cached_keys'],
        'max_cached_keys' => $status['opcache_statistics']['max_cached_keys'],
        'hits' => $status['opcache_statistics']['hits'],
        'misses' => $status['opcache_statistics']['misses'],
        'hit_rate' => round($status['opcache_statistics']['opcache_hit_rate'], 2),
    ],
]);
# Check OPcache status
curl http://localhost/opcache-status.php | jq

# Clear OPcache (when needed)
docker exec <container> kill -USR2 1  # Reload PHP-FPM

OPcache Sizing

# Count PHP files in your application
find /var/www/html -type f -name "*.php" | wc -l

# Example: 5000 files
# Set max_accelerated_files to next prime > 5000 * 2 = 10000
# Closest prime: 10007 or 20000 (safe)

OPcache memory calculation:

Average file size * Number of files * 2 = Memory needed
10KB * 5000 * 2 = 100MB

Add 50% buffer = 150MB minimum
Recommended: 256MB for production

Nginx Optimization

Worker Configuration

# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;  # CPU count
worker_rlimit_nofile 65535;
pid /run/nginx.pid;

events {
    worker_connections 4096;  # connections per worker
    use epoll;                # Linux optimization
    multi_accept on;
}

http {
    # Basic optimizations
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    types_hash_max_size 2048;
    server_tokens off;

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # Client settings
    client_max_body_size 100m;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # Timeouts
    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;

    # Include configs
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
}

FastCGI Optimization

# Increase buffer sizes for PHP-FPM
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

# Connection settings
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;

# Caching (optional)
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

Static Asset Caching

# Cache static assets
location ~* \.(jpg|jpeg|gif|png|webp|svg|ico|pdf)$ {
    expires 365d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location ~* \.(css|js|woff|woff2|ttf|eot)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

# Enable gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/ld+json
    application/manifest+json
    application/rss+xml
    application/vnd.geo+json
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/bmp
    image/svg+xml
    image/x-icon
    text/cache-manifest
    text/css
    text/plain
    text/vcard
    text/vnd.rim.location.xloc
    text/vtt
    text/x-component
    text/x-cross-domain-policy;

Open File Cache

# Cache file descriptors
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

Database Connection Pooling

PgBouncer (PostgreSQL)

services:
  pgbouncer:
    image: edoburu/pgbouncer:latest
    environment:
      - DB_HOST=postgres
      - DB_USER=app
      - DB_PASSWORD=${DB_PASSWORD}
      - POOL_MODE=transaction
      - MAX_CLIENT_CONN=1000
      - DEFAULT_POOL_SIZE=25
    ports:
      - "6432:6432"

  app:
    environment:
      - DB_HOST=pgbouncer
      - DB_PORT=6432

ProxySQL (MySQL)

services:
  proxysql:
    image: proxysql/proxysql:latest
    ports:
      - "6033:6033"
    volumes:
      - ./proxysql.cnf:/etc/proxysql.cnf
    depends_on:
      - mysql

  app:
    environment:
      - DB_HOST=proxysql
      - DB_PORT=6033

Laravel Connection Pooling

// config/database.php
'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST'),
    'options' => [
        PDO::ATTR_PERSISTENT => true,  // Persistent connections
    ],
    'sticky' => true,
],

Caching Strategies

APCu for Local Cache

// Laravel cache config
'stores' => [
    'apcu' => [
        'driver' => 'apcu',
    ],
],

// Usage
Cache::store('apcu')->remember('users', 3600, function () {
    return DB::table('users')->get();
});

Redis for Distributed Cache

Optimized Redis configuration:

# redis.conf
maxmemory 512mb
maxmemory-policy allkeys-lru

# Persistence (optional)
save 900 1
save 300 10
save 60 10000

# Performance
tcp-backlog 511
timeout 0
tcp-keepalive 300

Laravel Redis optimization:

// config/cache.php
'redis' => [
    'driver' => 'redis',
    'connection' => 'cache',
    'lock_connection' => 'default',
],

// config/database.php
'redis' => [
    'client' => 'phpredis',  // Faster than predis
    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME'), '_').'_database_'),
    ],
    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT'),
        'database' => env('REDIS_CACHE_DB', 1),
    ],
],

HTTP Caching Headers

location / {
    # Cache-Control for dynamic content
    add_header Cache-Control "private, no-cache, no-store, must-revalidate";
    add_header Pragma "no-cache";
    add_header Expires 0;
}

location /api/ {
    # API responses with short cache
    add_header Cache-Control "public, max-age=300";
}

location ~* \.(jpg|jpeg|gif|png|webp|svg)$ {
    # Long-term caching for images
    add_header Cache-Control "public, max-age=31536000, immutable";
}

Resource Limits

Container Resource Limits

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '1.0'
          memory: 2G

PHP Memory Limits

services:
  app:
    environment:
      # Set based on application needs
      - PHP_MEMORY_LIMIT=512M  # Per-request memory

Calculate safe limits:

Total container memory: 4GB
PHP-FPM processes: 50
Safe memory per process: 4GB / 50 = ~80MB
Set PHP_MEMORY_LIMIT: 80M * 0.8 = 64M (with buffer)

Nginx Connection Limits

# Limit connections per IP
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_conn conn_limit_per_ip 10;

# Rate limiting
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;
limit_req zone=req_limit_per_ip burst=20 nodelay;

Monitoring Performance

PHP-FPM Status Page

Enable in PHP-FPM config:

pm.status_path = /fpm-status
location /fpm-status {
    access_log off;
    allow 127.0.0.1;
    deny all;
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Check status
curl http://localhost/fpm-status?full

# JSON format
curl http://localhost/fpm-status?json

Prometheus Metrics

services:
  php-fpm-exporter:
    image: hipages/php-fpm_exporter:latest
    ports:
      - "9253:9253"
    environment:
      - PHP_FPM_SCRAPE_URI=tcp://app:9000/status

  nginx-exporter:
    image: nginx/nginx-prometheus-exporter:latest
    ports:
      - "9113:9113"
    command:
      - -nginx.scrape-uri=http://app/nginx_status

Application Performance Monitoring (APM)

New Relic:

FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.3-bookworm

RUN curl -L https://download.newrelic.com/php_agent/release/newrelic-php5-10.x-linux.tar.gz | tar -C /tmp -zx \
    && export NR_INSTALL_USE_CP_NOT_LN=1 \
    && export NR_INSTALL_SILENT=1 \
    && /tmp/newrelic-php5-*/newrelic-install install \
    && rm -rf /tmp/newrelic-php5-*

Blackfire:

services:
  blackfire:
    image: blackfire/blackfire:2
    environment:
      - BLACKFIRE_SERVER_ID=${BLACKFIRE_SERVER_ID}
      - BLACKFIRE_SERVER_TOKEN=${BLACKFIRE_SERVER_TOKEN}

Performance Checklist

✅ PHP-FPM

  • Process manager tuned for workload
  • max_children calculated based on memory
  • max_requests set to prevent memory leaks
  • request_terminate_timeout configured
  • Slow request logging enabled

✅ OPcache

  • OPcache enabled and sized correctly
  • validate_timestamps = 0 in production
  • max_accelerated_files > total PHP files
  • JIT enabled (PHP 8.0+)
  • Monitoring in place

✅ Nginx

  • Worker processes = CPU count
  • Worker connections increased
  • FastCGI buffers optimized
  • Static asset caching enabled
  • Gzip compression configured
  • Open file cache enabled

✅ Caching

  • OPcache for PHP bytecode
  • APCu for local data cache
  • Redis for distributed cache
  • HTTP caching headers set
  • Database query caching enabled

✅ Monitoring

  • PHP-FPM status page enabled
  • Nginx metrics exposed
  • APM tool integrated
  • Log aggregation configured
  • Alerts set up

Questions? Check common issues or ask in GitHub Discussions.