• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and DynamoDB on OVH for Laravel

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and DynamoDB on OVH for Laravel

Nginx as a High-Performance Frontend for Laravel

When deploying Laravel applications, Nginx serves as an excellent choice for a web server and reverse proxy. Its event-driven, asynchronous architecture makes it highly efficient for handling concurrent connections. For a Laravel application, we’ll focus on optimizing Nginx for static file serving, SSL termination, and efficient proxying to our PHP process manager.

Nginx Configuration for Laravel

A robust Nginx configuration for a Laravel application typically involves several key directives. We’ll assume a standard OVH setup with PHP-FPM and a single-page application (SPA) routing pattern, common for modern Laravel apps.

Core Server Block

This block defines the primary listening ports, server name, and SSL configuration. Ensure your SSL certificates are correctly placed and referenced.

server {
    listen 80;
    listen [::]:80;
    server_name your_domain.com www.your_domain.com;
    return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your_domain.com www.your_domain.com;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s; # Google DNS, adjust as needed
    resolver_timeout 5s;

    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Adjust as needed

    # Gzip Compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # Root and Index
    root /var/www/your_laravel_app/public;
    index index.php index.html index.htm;

    # Static File Caching
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # PHP-FPM Configuration
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version and socket path
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300; # Increase timeout for long-running PHP scripts
    }

    # Deny access to .env and other sensitive files
    location ~ /\.env { deny all; }
    location ~ /\.git { deny all; }
    location ~ /\.env\.example { deny all; }
    location ~ /\.htaccess { deny all; }
    location ~ /\.composer { deny all; }
    location ~ /\.config { deny all; }

    # Laravel SPA Routing
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
    }

    # Error Pages
    error_page 404 /index.php; # Laravel handles 404s
    error_page 500 502 503 504 /index.php; # Laravel handles these too
}

Key Optimizations Explained:

  • HTTP/2: Enabled for faster multiplexing and header compression.
  • SSL Configuration: Strong cipher suites and TLS versions are crucial. OCSP stapling improves handshake performance.
  • Security Headers: Essential for protecting against common web vulnerabilities.
  • Gzip Compression: Reduces bandwidth usage and speeds up asset delivery.
  • Static File Caching: Long expiry times for static assets with `immutable` cache control.
  • PHP-FPM Proxy: Directs PHP requests to the FPM worker pool. fastcgi_read_timeout is increased to prevent timeouts on lengthy operations.
  • SPA Routing: try_files directive ensures that all non-file requests are passed to index.php, allowing Laravel’s router to handle them.
  • Sensitive File Protection: Prevents direct access to configuration and source files.

Gunicorn/PHP-FPM Tuning for Laravel

For PHP applications like Laravel, PHP-FPM (FastCGI Process Manager) is the standard. Tuning its configuration is critical for handling load effectively. We’ll focus on the php-fpm.conf and pool configuration files.

PHP-FPM Pool Configuration

The pool configuration (e.g., /etc/php/8.1/fpm/pool.d/www.conf) dictates how PHP processes are managed. The choice between static, dynamic, and ondemand process management depends on your application’s traffic patterns and server resources.

; /etc/php/8.1/fpm/pool.d/www.conf

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process Manager Settings
; pm = dynamic ; Default
pm = ondemand ; Recommended for variable traffic, conserves resources when idle
; pm = static ; Best for constant high traffic, but uses more memory

; Dynamic Process Management
; pm.max_children = 50
; pm.start_servers = 5
; pm.min_spare_servers = 2
; pm.max_spare_servers = 10
; pm.process_idle_timeout = 10s

; OnDemand Process Management
pm.max_children = 100 ; Maximum number of children that can be started.
pm.start_time = '200s' ; The time after which a child process may be killed if inactive.
pm.max_requests = 500 ; Maximum number of requests each child process should execute.

; Other important settings
request_terminate_timeout = 300 ; Corresponds to fastcgi_read_timeout in Nginx
; rlimit_files = 4096
; rlimit_nofile = 65536
; pm.max_children = 100 ; Adjust based on available RAM. Rule of thumb: (Total RAM - OS/Nginx RAM) / Average PHP Process RAM
; pm.max_requests = 1000 ; Prevents memory leaks from accumulating over time.

Tuning Parameters:

  • pm:
    • dynamic: PHP-FPM spawns processes as needed, up to pm.max_children. Good balance.
    • ondemand: Processes are only created when a request comes in and are killed after a timeout (pm.process_idle_timeout or pm.start_time). Excellent for reducing memory usage during low traffic.
    • static: A fixed number of processes are always kept alive. Best for predictable, high-traffic scenarios.
  • pm.max_children: This is the most critical setting. It directly impacts memory usage. Calculate this based on your server’s RAM. A common approach: (Total RAM - (OS RAM + Nginx RAM + MySQL RAM)) / Average PHP Process RAM. Monitor memory usage with htop or similar tools.
  • pm.max_requests: Setting this to a reasonable value (e.g., 500-1000) helps mitigate memory leaks in long-running PHP processes.
  • request_terminate_timeout: Should generally match or be slightly higher than Nginx’s fastcgi_read_timeout to avoid premature termination of long-running scripts.

DynamoDB Performance Tuning for Laravel

When using DynamoDB with Laravel, especially for caching or session storage, performance hinges on proper table design, provisioned throughput, and efficient querying. OVH does not directly offer DynamoDB, so this section assumes you are using AWS DynamoDB or a compatible managed service.

Table Design and Indexing

A well-designed DynamoDB table is fundamental. Choose your Partition Key (PK) and Sort Key (SK) wisely to distribute data evenly and support your query patterns.

  • Partition Key (PK): Aim for high cardinality. Avoid hot partitions where a single PK receives a disproportionate amount of traffic.
  • Sort Key (SK): Enables efficient range queries and sorting within a partition.
  • Global Secondary Indexes (GSIs): Use GSIs to support query patterns that don’t align with the base table’s keys. Be mindful that GSIs consume their own provisioned throughput.
  • Local Secondary Indexes (LSIs): Useful for supporting different sort orders within the same partition key. LSIs share the PK with the base table and have a different SK. They must be created at table creation time.

Provisioned Throughput Management

DynamoDB operates on a provisioned throughput model (Read Capacity Units – RCUs, Write Capacity Units – WCUs). Over-provisioning is expensive, while under-provisioning leads to throttling.

  • Monitor Usage: Regularly check CloudWatch metrics for ConsumedReadCapacityUnits and ConsumedWriteCapacityUnits.
  • Identify Throttling: Look for ReadThrottleEvents and WriteThrottleEvents.
  • Auto Scaling: Configure DynamoDB Auto Scaling to automatically adjust provisioned throughput based on actual usage. This is crucial for cost optimization and performance stability.
{
    "TableName": "YourLaravelCacheTable",
    "KeySchema": [
        { "AttributeName": "pk", "KeyType": "HASH" },
        { "AttributeName": "sk", "KeyType": "RANGE" }
    ],
    "AttributeDefinitions": [
        { "AttributeName": "pk", "AttributeType": "S" },
        { "AttributeName": "sk", "AttributeType": "S" }
    ],
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 10,
        "WriteCapacityUnits": 10
    },
    "GlobalSecondaryIndexes": [
        {
            "IndexName": "GSI1",
            "KeySchema": [
                { "AttributeName": "gsi1pk", "KeyType": "HASH" },
                { "AttributeName": "gsi1sk", "KeyType": "RANGE" }
            ],
            "Projection": {
                "ProjectionType": "ALL"
            },
            "ProvisionedThroughput": {
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            }
        }
    ]
}

Auto Scaling Configuration Example (AWS CLI):

# Enable Auto Scaling for the main table
aws application-autoscaling register-scalable-target \
    --service-namespace dynamodb \
    --resource-id table/YourLaravelCacheTable \
    --scalable-dimension dynamodb:table:WriteCapacityUnits \
    --min-capacity 5 \
    --max-capacity 50

aws application-autoscaling register-scalable-target \
    --service-namespace dynamodb \
    --resource-id table/YourLaravelCacheTable \
    --scalable-dimension dynamodb:table:ReadCapacityUnits \
    --min-capacity 5 \
    --max-capacity 50

# Define scaling policies
aws application-autoscaling put-scaling-policy \
    --service-namespace dynamodb \
    --resource-id table/YourLaravelCacheTable \
    --scalable-dimension dynamodb:table:WriteCapacityUnits \
    --policy-name WriteCapacityScalingPolicy \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{
        "TargetValue": 70.0,
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "DynamoDBWriteCapacityUtilization"
        },
        "ScaleInCooldown": 60,
        "ScaleOutCooldown": 60
    }'

aws application-autoscaling put-scaling-policy \
    --service-namespace dynamodb \
    --resource-id table/YourLaravelCacheTable \
    --scalable-dimension dynamodb:table:ReadCapacityUnits \
    --policy-name ReadCapacityScalingPolicy \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{
        "TargetValue": 70.0,
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "DynamoDBReadCapacityUtilization"
        },
        "ScaleInCooldown": 60,
        "ScaleOutCooldown": 60
    }'

The TargetValue of 70% is a common starting point, allowing headroom for spikes while scaling down aggressively when idle. Adjust min-capacity and max-capacity based on your expected traffic and budget.

Efficient Querying in Laravel

When interacting with DynamoDB from Laravel, use the AWS SDK for PHP. Optimize your queries to minimize consumed capacity.

use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;

$marshaler = new Marshaler();

$client = new DynamoDbClient([
    'region'  => 'us-east-1', // Your AWS region
    'version' => 'latest',
    // Add credentials or IAM role configuration here
]);

// Example: Storing session data
$sessionId = session()->getId();
$sessionData = session()->all(); // Be mindful of session data size

$params = [
    'TableName' => 'YourLaravelSessionTable',
    'Item' => $marshaler->marshalItem([
        'pk' => 'SESSION#' . $sessionId,
        'sk' => 'METADATA',
        'data' => serialize($sessionData), // Serialize for storage
        'expires_at' => time() + config('session.lifetime') * 60, // TTL in seconds
    ]),
];

try {
    $client->putItem($params);
} catch (AwsException $e) {
    // Handle error
    error_log("DynamoDB Session Save Error: " . $e->getMessage());
}

// Example: Retrieving session data
$sessionId = session()->getId();
$params = [
    'TableName' => 'YourLaravelSessionTable',
    'Key' => $marshaler->marshalItem([
        'pk' => 'SESSION#' . $sessionId,
        'sk' => 'METADATA',
    ]),
];

try {
    $result = $client->getItem($params);
    if (isset($result['Item'])) {
        $item = $marshaler->unmarshalItem($result['Item']);
        // Unserialize and load session data
        session()->replace(unserialize($item['data']));
    }
} catch (AwsException $e) {
    // Handle error
    error_log("DynamoDB Session Load Error: " . $e->getMessage());
}

// Example: Using a GSI for querying
// Assume GSI1 is configured with gsi1pk and gsi1sk
$params = [
    'TableName' => 'YourLaravelDataTable',
    'IndexName' => 'GSI1',
    'KeyConditionExpression' => 'gsi1pk = :pk_val AND gsi1sk BETWEEN :sk_start AND :sk_end',
    'ExpressionAttributeValues' => $marshaler->marshalItem([
        ':pk_val' => 'USER#123',
        ':sk_start' => 'ORDER#2023-01-01',
        ':sk_end' => 'ORDER#2023-12-31',
    ]),
];

try {
    $result = $client->query($params);
    // Process $result['Items']
} catch (AwsException $e) {
    // Handle error
    error_log("DynamoDB Query Error: " . $e->getMessage());
}

Key Considerations:

  • Serialization: For session data or complex objects, serialization (like PHP’s serialize() or JSON encoding) is often necessary. Be mindful of the size of serialized data, as it impacts storage costs and read/write times.
  • TTL (Time To Live): DynamoDB’s TTL feature is excellent for automatically expiring session data or cache entries, reducing manual cleanup and storage costs.
  • Query vs. Scan: Always prefer Query operations over Scan. Scans read the entire table and are very inefficient and costly. Design your keys and indexes to support efficient queries.
  • Batch Operations: Use BatchGetItem and BatchWriteItem for multiple read/write operations to reduce network latency and improve efficiency, but be aware of their limitations (e.g., 25 items per batch).

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala