• 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 » Scaling Laravel on OVH to Handle 50,000+ Concurrent Requests

Scaling Laravel on OVH to Handle 50,000+ Concurrent Requests

Architectural Foundation: Beyond Single Instances

Achieving 50,000+ concurrent requests for a Laravel application on OVH necessitates a fundamental shift from a monolithic, single-server deployment to a distributed, horizontally scalable architecture. This involves decoupling core components, leveraging managed services where appropriate, and implementing robust load balancing and caching strategies. We’ll focus on a typical OVH Public Cloud setup, assuming a baseline of compute instances, managed databases, and potentially object storage.

Load Balancing Strategy: HAProxy for High Availability

A critical first step is implementing a high-availability load balancer. While OVH offers its own load balancing services, deploying and managing HAProxy on a dedicated instance provides granular control and deep insights. We’ll configure HAProxy for TCP and HTTP load balancing, health checks, and sticky sessions if absolutely necessary (though stateless is preferred).

HAProxy Configuration for Laravel

This configuration assumes you have at least two Laravel application servers (e.g., `app_server_1` and `app_server_2`) running your PHP-FPM instances on port 9000, and your web server (Nginx) listening on port 80.

global
    log         127.0.0.1 local0
    log         127.0.0.1 local1 notice
    maxconn     4096
    user        haproxy
    group       haproxy
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client  50000
    timeout server  50000
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

frontend http_frontend
    bind *:80
    acl host_static       hdr(host) -i static.yourdomain.com
    acl host_api          hdr(host) -i api.yourdomain.com
    acl host_app          hdr(host) -i app.yourdomain.com

    # Route static assets to a dedicated Nginx instance or CDN
    use_backend static_servers if host_static

    # Route API requests to a dedicated backend pool
    use_backend api_servers if host_api

    # Default to the main application servers
    default_backend app_servers

backend app_servers
    balance roundrobin
    option httpchk GET /healthz HTTP/1.1\r\nHost:\ app.yourdomain.com
    server app_server_1 192.168.1.10:80 check
    server app_server_2 192.168.1.11:80 check
    # Add more app servers as needed

backend api_servers
    balance roundrobin
    option httpchk GET /healthz HTTP/1.1\r\nHost:\ api.yourdomain.com
    server api_server_1 192.168.1.20:80 check
    server api_server_2 192.168.1.21:80 check
    # Add more API servers if they are separate

backend static_servers
    balance roundrobin
    server static_server_1 192.168.1.30:80 check
    # Or point to a CDN endpoint

listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /haproxy?stats
    stats realm Haproxy\ Statistics
    stats auth admin:YourSecurePassword

Key Considerations:

  • Health Checks: The option httpchk directive is crucial. Ensure your Laravel application has a dedicated health check endpoint (e.g., /healthz) that returns a 200 OK status code. This endpoint should be lightweight and not perform expensive database queries.
  • Backend Servers: Replace the placeholder IP addresses with the actual private IPs of your Nginx/Laravel application servers within your OVH VPC.
  • Statelessness: Design your Laravel application to be stateless. Avoid storing session data on the application servers themselves. Use a distributed cache like Redis or Memcached for sessions.
  • Dedicated API/Static Backends: For optimal performance, consider separating API endpoints and static asset serving into distinct backend pools, potentially even on different server clusters or using a CDN.
  • HAProxy Stats: The listen stats section provides a web interface for monitoring HAProxy. Secure it with strong authentication.

Application Server Optimization: Nginx & PHP-FPM Tuning

Each application server needs to be meticulously tuned. This involves optimizing Nginx for high concurrency and PHP-FPM for efficient request processing.

Nginx Configuration (`nginx.conf` or site-specific conf)

user www-data;
worker_processes auto; # Set to the number of CPU cores
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096; # Adjust based on system limits and expected load
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off; # Hide Nginx version for security

    # 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;

    # Buffers and Timeouts
    client_body_buffer_size 10M;
    client_max_body_size 10M;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 32k;
    output_buffers 1 32k;
    post_action @fallback; # For handling upstream timeouts gracefully

    # FastCGI (PHP-FPM) configuration
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_read_timeout 300; # Increase timeout for long-running scripts
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 300;
    }

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

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

    # Include other configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

# Fallback for upstream timeouts (optional but recommended)
location @fallback {
    return 504 "Gateway Timeout";
}

PHP-FPM Configuration (`php-fpm.conf` and pool configuration)

Edit the PHP-FPM pool configuration file (e.g., `/etc/php/8.1/fpm/pool.d/www.conf`).

; Basic settings
pid = /run/php/php8.1-fpm.pid
error_log = /var/log/php8.1-fpm.log
log_level = notice

; Process management
; Use 'dynamic' for typical scenarios, 'static' for predictable high load
pm = dynamic
pm.max_children = 250       ; Adjust based on available RAM and CPU
pm.start_servers = 50       ; Initial number of children
pm.min_spare_servers = 25   ; Minimum idle children
pm.max_spare_servers = 100  ; Maximum idle children
pm.max_requests = 5000      ; Restart children after this many requests

; Performance tuning
request_terminate_timeout = 120 ; Max execution time for a script (seconds)
; Use 'ondemand' if you have very spiky traffic and want to save memory
; pm.process_idle_timeout = 10s ; For 'ondemand'

; Listen on a Unix socket for Nginx
listen = /var/run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Security settings
; security.limit_extensions = .php .phtml ; Uncomment and adjust if needed

Tuning Notes:

  • `worker_processes` (Nginx): Set this to the number of CPU cores on your Nginx server.
  • `worker_connections` (Nginx): This defines the maximum number of simultaneous connections a worker can handle. The theoretical limit is `worker_processes * worker_connections`. Ensure your system’s file descriptor limits (`ulimit -n`) are high enough.
  • `pm.max_children` (PHP-FPM): This is the most critical setting. It dictates how many PHP processes can run concurrently. A common formula is `(Total RAM – RAM for OS/Nginx) / Average RAM per PHP process`. Monitor memory usage closely.
  • `fastcgi_read_timeout` (Nginx): Increase this if your Laravel application has long-running tasks.
  • `request_terminate_timeout` (PHP-FPM): Corresponds to PHP’s `max_execution_time`.
  • Socket vs. TCP: Using Unix sockets (`/var/run/php/phpX.Y-fpm.sock`) is generally faster than TCP/IP for communication between Nginx and PHP-FPM on the same server.

Database Scaling: OVH Managed PostgreSQL/MySQL

Your database is often the bottleneck. Relying on OVH’s managed database services (e.g., Managed PostgreSQL or Managed MySQL) is highly recommended for scalability and reliability. These services offer features like read replicas, automated backups, and failover.

Read Replicas for Offloading Reads

Configure at least one read replica for your primary database. Your Laravel application can then be configured to use the replica for read-heavy operations.

// config/database.php

'connections' => [

    'mysql' => [
        'driver' => 'mysql',
        'url' => env('DATABASE_URL'),
        'host' => env('DB_HOST', '127.0.0.1'), // Primary DB host
        'port' => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        // ... other primary connection settings
    ],

    'mysql_read' => [
        'driver' => 'mysql',
        'url' => env('DATABASE_URL_READ'),
        'host' => env('DB_HOST_READ', 'db-read-replica.ovh.local'), // Read replica host
        'port' => env('DB_PORT_READ', '3306'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'options' => [
            PDO::ATTR_TIMEOUT => 5, // Shorter timeout for replicas
        ],
    ],

],

'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],

    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],

    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_CACHE_DB', 1), // Use a separate DB for cache
    ],
],

In your Laravel application code, you can then explicitly use the read connection:

use Illuminate\Support\Facades\DB;

// Using the default (write) connection
$users = DB::connection('mysql')->select('SELECT * FROM users');

// Using the read replica connection
$products = DB::connection('mysql_read')->select('SELECT * FROM products WHERE is_active = 1');

// Eloquent models can also be configured to use specific connections
// (Requires defining connection properties on the model or using traits)

Database Indexing and Query Optimization

Even with replicas, poorly optimized queries will cripple performance. Regularly analyze your slow query logs and ensure appropriate indexes are in place. Use tools like Laravel Debugbar in development and `EXPLAIN` in production to identify bottlenecks.

-- Example: Analyzing a slow query
EXPLAIN SELECT p.*, c.name as category_name
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.price > 100
ORDER BY p.created_at DESC;

-- If 'category_id' or 'price' are not indexed, add them:
CREATE INDEX idx_products_category_id ON products (category_id);
CREATE INDEX idx_products_price ON products (price);

Caching Strategies: Redis for Everything

Aggressive caching is non-negotiable. Redis is an excellent choice for various caching layers in a Laravel application.

Leveraging Laravel’s Cache Facade

Ensure your .env file is configured to use Redis for caching:

CACHE_DRIVER=redis
REDIS_HOST=your-redis-host.ovh.internal
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=1

Implement caching for:

  • Configuration: Laravel automatically caches config files. Run `php artisan config:cache`.
  • Routes: Cache your routes for faster lookup. Run `php artisan route:cache`.
  • Views: Compile and cache Blade views. Run `php artisan view:cache`.
  • Database Query Results: Cache expensive or frequently accessed query results.
  • API Responses: Cache responses from external APIs or computationally intensive internal endpoints.
  • Sessions: As mentioned, use Redis for session storage to maintain statelessness.
use Illuminate\Support\Facades\Cache;
use App\Models\Product;

// Example: Caching a collection of products
$products = Cache::remember('all_active_products', now()->addMinutes(60), function () {
    return Product::where('is_active', true)->get();
});

// Example: Caching a single model instance
$user = Cache::remember('user_' . $userId, now()->addHours(1), function () use ($userId) {
    return User::findOrFail($userId);
});

// Example: Caching an API response
$apiResponse = Cache::remember('external_api_data_' . $cacheKey, now()->addMinutes(15), function () use ($apiEndpoint) {
    // Make the API call here
    $client = new \GuzzleHttp\Client();
    $response = $client->get($apiEndpoint);
    return json_decode($response->getBody(), true);
});

Queueing for Background Jobs

Any time-consuming task that doesn’t need to be part of the immediate HTTP request-response cycle should be offloaded to a background queue. This includes sending emails, processing images, generating reports, etc.

Redis as a Queue Driver

Configure your .env file to use Redis for queues:

QUEUE_CONNECTION=redis

Ensure you have Redis running and accessible. You can use OVH’s Managed Redis service or deploy your own.

Running Queue Workers

On your application servers (or dedicated worker servers), you need to run the queue worker processes. Use a process manager like `supervisor` to keep them running reliably.

# Start a queue worker
php artisan queue:work --queue=default,high_priority --tries=3 --timeout=300

# Start multiple workers for different queues or to increase concurrency
# Example supervisor configuration for a single worker:
# /etc/supervisor/conf.d/laravel-queue.conf

[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work redis --queue=default,high_priority --sleep=3 --tries=3 --timeout=300
autostart=true
autorestart=true
user=www-data
numprocs=4 ; Number of concurrent workers for this queue
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-queue.log
stderr_logfile=/var/log/supervisor/laravel-queue.err.log

Key Queue Settings:

  • `–tries`: Number of times to attempt a failed job before giving up.
  • `–timeout`: Maximum number of seconds a job can run.
  • `–sleep`: Seconds to sleep if the queue is empty.
  • `–memory`: Maximum amount of memory a worker may consume.
  • `–queue`: Specify which queues the worker should process.

Monitoring and Alerting

A high-traffic system requires constant vigilance. Implement comprehensive monitoring for all components.

  • Server Metrics: CPU, RAM, Disk I/O, Network Traffic (using tools like Prometheus/Grafana, Datadog, or OVH’s monitoring).
  • Application Performance Monitoring (APM): Tools like New Relic, Datadog APM, or Sentry can provide deep insights into request tracing, database query times, and errors within your Laravel application.
  • HAProxy Stats: Regularly check the HAProxy stats page for backend server health, request rates, and error counts.
  • Queue Monitoring: Monitor the number of pending jobs in your Redis queue. Use tools like Laravel Horizon for a robust dashboard.
  • Log Aggregation: Centralize logs from all servers (Nginx, PHP-FPM, Laravel logs) using a tool like the ELK stack (Elasticsearch, Logstash, Kibana) or Graylog.

Deployment and CI/CD

Automate your deployments to ensure consistency and reduce manual errors. A CI/CD pipeline is essential for managing multiple application servers.

  • Version Control: Git is mandatory.
  • CI/CD Platform: GitLab CI, GitHub Actions, Jenkins, CircleCI.
  • Deployment Strategy: Blue-Green deployments or Rolling Updates to minimize downtime.
  • Configuration Management: Tools like Ansible or Chef can automate server setup and configuration.
  • Zero-Downtime Deployments: Ensure your deployment process involves updating application servers one by one, with the load balancer taking them out of rotation during the update.

Conclusion: Iterative Scaling

Scaling to 50,000+ concurrent requests is not a one-time task but an ongoing process. Start with these foundational elements: robust load balancing, optimized application servers, scalable database solutions, and aggressive caching. Continuously monitor performance, identify bottlenecks, and iterate on your architecture. OVH’s Public Cloud provides the building blocks, but a well-architected Laravel application and meticulous tuning are paramount.

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

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala