The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on OVH for Laravel
Optimizing Nginx for Laravel on OVH: A Deep Dive
This section focuses on fine-tuning Nginx as the front-facing web server for your Laravel application hosted on OVH. We’ll cover essential directives for performance, security, and efficient resource utilization.
Nginx Configuration for Laravel
A robust Nginx configuration is paramount. We’ll start with a typical setup and then introduce specific optimizations.
Core Server Block Structure
This is a foundational server block. Pay close attention to the try_files directive, which is crucial for Laravel’s routing.
server {
listen 80;
listen [::]:80;
server_name your_domain.com www.your_domain.com;
root /var/www/your_laravel_app/public; # Adjust to your Laravel public directory
index index.php index.html index.htm;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ /index.php?$query_string;
}
# Pass PHP scripts to the PHP-FPM listener
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm:
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;
}
# Deny access to .htaccess files, if any
location ~ /\.ht {
deny all;
}
# Prevent access to hidden files
location ~ /\. {
deny all;
}
# Caching for static assets
location ~* \.(css|js|jpg|jpeg|gif|png|ico|svg|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
access_log off;
}
# 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;
}
Tuning Nginx Worker Processes and Connections
The worker_processes and worker_connections directives are critical for handling concurrent requests. On OVH servers, especially VPS instances, you’ll want to tune these based on your CPU cores and available RAM.
To determine the optimal number of worker processes, a good starting point is to match the number of CPU cores available to your instance. You can check this with nproc.
nproc
Then, set worker_processes in /etc/nginx/nginx.conf. For a 4-core VPS, worker_processes 4; is a reasonable starting point. For hyper-threaded CPUs, you might experiment with auto.
# In /etc/nginx/nginx.conf
user www-data;
worker_processes auto; # Or specify number of CPU cores, e.g., 4;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024; # Adjust based on RAM and expected load
# multi_accept on; # Can improve performance on busy servers
}
worker_connections defines the maximum number of simultaneous connections that each worker process can handle. The total maximum connections will be worker_processes * worker_connections. A common value is 1024, but this can be increased if you have ample RAM and expect high concurrency. Monitor your server’s load and connection counts to find the sweet spot.
Enabling HTTP/2
HTTP/2 offers significant performance improvements over HTTP/1.1. Ensure your OVH SSL certificate is correctly configured and then enable HTTP/2 in your server block.
server {
listen 443 ssl http2; # Enable SSL and HTTP/2
listen [::]:443 ssl http2;
server_name your_domain.com www.your_domain.com;
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem; # Adjust path
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem; # Adjust path
# ... rest of your server block ...
}
Gunicorn/PHP-FPM Tuning for Laravel
The choice between Gunicorn (for Python-based frameworks) and PHP-FPM (for PHP) dictates the tuning strategy. Since the prompt mentions Laravel, we’ll focus on PHP-FPM. If you were using a Python framework like Django or Flask, Gunicorn tuning would be the focus.
PHP-FPM Configuration Tuning
PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with Nginx. Its configuration significantly impacts application responsiveness and resource usage.
The primary configuration file is typically located at /etc/php/8.1/fpm/php-fpm.conf (adjust version as needed). Within this file, the pool.d/www.conf (or a similarly named file in pool.d/) is where most tuning occurs.
Process Manager Settings
The pm directive controls how PHP-FPM manages worker processes. The most common and recommended settings for production are dynamic or ondemand.
pm = dynamic: This is a good balance. PHP-FPM starts a few children, and if the load increases, it spawns more up topm.max_children. When idle, it can kill off excess children down topm.min_spare_servers.pm = ondemand: Processes are only created when a request comes in. This can save memory when idle but might introduce slight latency on the first request after an idle period.pm = static: A fixed number of children are always kept running. This offers the most consistent performance but can be wasteful of resources if the load is highly variable.
For a typical Laravel application on OVH, dynamic is often the best starting point. Let’s configure it:
; In /etc/php/8.1/fpm/pool.d/www.conf pm = dynamic pm.max_children = 50 ; Adjust based on RAM and Nginx worker_connections pm.min_spare_servers = 5 ; Minimum idle servers pm.max_spare_servers = 10 ; Maximum idle servers pm.start_servers = 2 ; Initial number of servers to start pm.process_idle_timeout = 10s; For pm = dynamic, idle timeout before killing pm.max_requests = 500 ; Restart processes after this many requests to prevent memory leaks
Tuning pm.max_children: This is the most critical setting. It should be calculated based on your server’s RAM and the memory footprint of your Laravel application. A common formula is: (Total RAM - Reserved RAM for OS/Nginx/Redis) / Average PHP process memory. Start conservatively and monitor memory usage. If you see OOM killer events or high swap usage, reduce this value. If your server has plenty of free RAM and is still struggling, you might increase it.
Tuning pm.max_requests: Setting this to a reasonable number (e.g., 500-1000) helps prevent memory leaks from accumulating over time by periodically restarting worker processes. This is a good practice for long-running applications.
Tuning Request Handling and Timeout
The request_terminate_timeout and request_slowlog_timeout are important for managing long-running requests and identifying performance bottlenecks.
; In /etc/php/8.1/fpm/pool.d/www.conf request_terminate_timeout = 60s ; Terminate scripts exceeding this time request_slowlog_timeout = 10s ; Log scripts exceeding this time to slowlog file slowlog = /var/log/php/php8.1-fpm.slow.log ; Path to slow log file
Ensure the directory for slowlog exists and is writable by the PHP-FPM user (e.g., www-data).
sudo mkdir -p /var/log/php sudo chown www-data:www-data /var/log/php
Restarting PHP-FPM
After making changes to PHP-FPM configuration, always restart the service:
sudo systemctl restart php8.1-fpm # Adjust PHP version as needed
Redis Performance Tuning on OVH
Redis is an excellent choice for caching in Laravel, significantly reducing database load. Proper configuration is key to its effectiveness.
Redis Configuration for Caching
The main configuration file is /etc/redis/redis.conf. We’ll focus on directives relevant to caching performance and stability.
Memory Management
maxmemory is crucial to prevent Redis from consuming all available RAM. Set this to a value that leaves enough memory for your OS, Nginx, and PHP-FPM. A common strategy is to allocate a fixed portion of your server’s RAM.
# In /etc/redis/redis.conf maxmemory 512mb # Example: Allocate 512MB for Redis maxmemory-policy allkeys-lru # Evict least recently used keys when maxmemory is reached
The maxmemory-policy determines how Redis evicts keys when the memory limit is reached. allkeys-lru (Least Recently Used) is generally a good default for caching, as it removes the least recently accessed items first.
Persistence (RDB vs. AOF)
For a cache-only Redis instance, persistence can often be disabled or configured minimally to reduce I/O overhead and improve performance. If Redis is also used for critical data that must not be lost, persistence is essential.
To disable RDB snapshots:
# In /etc/redis/redis.conf save "" # Disable RDB snapshots
If you need persistence, consider AOF (Append Only File) with appendfsync everysec for a balance between durability and performance. For pure caching, disabling persistence is often preferred.
Network and Client Settings
Binding Redis to a specific IP address enhances security. If Redis is only accessed by your web server on the same machine, binding to 127.0.0.1 is recommended.
# In /etc/redis/redis.conf bind 127.0.0.1 # Or your server's private IP if accessed from other internal servers
tcp-backlog can be increased to handle a higher volume of incoming connections, especially under heavy load.
# In /etc/redis/redis.conf tcp-backlog 511 # Default is 511, can be increased if needed, e.g., 1024
Restarting Redis
Apply configuration changes by restarting the Redis service:
sudo systemctl restart redis-server
Monitoring and Diagnostics
Continuous monitoring is essential to validate your tuning efforts and identify new bottlenecks. Use a combination of system tools and application-level metrics.
Nginx Monitoring
Check Nginx status and error logs:
sudo systemctl status nginx sudo tail -f /var/log/nginx/error.log sudo tail -f /var/log/nginx/access.log
Use nginx -t to test configuration syntax before reloading.
sudo nginx -t sudo systemctl reload nginx
PHP-FPM Monitoring
Check PHP-FPM status and slow logs:
sudo systemctl status php8.1-fpm # Adjust PHP version sudo tail -f /var/log/php/php8.1-fpm.log # General logs sudo tail -f /var/log/php/php8.1-fpm.slow.log # Slow script logs
You can also use php-fpm -t to test configuration syntax.
sudo php-fpm8.1 -t # Adjust PHP version sudo systemctl reload php8.1-fpm # Adjust PHP version
Redis Monitoring
Use the Redis CLI for real-time insights:
redis-cli 127.0.0.1:6379> INFO memory 127.0.0.1:6379> INFO stats 127.0.0.1:6379> INFO persistence 127.0.0.1:6379> MONITOR # Use with caution, can be very verbose
INFO memory will show your current memory usage and maxmemory. INFO stats provides hit/miss ratios for the cache, crucial for evaluating Redis’s effectiveness.
Check Redis logs for errors:
sudo tail -f /var/log/redis/redis-server.log
Laravel Application-Level Optimizations
While infrastructure tuning is vital, application-level optimizations in Laravel are equally important. Ensure you’re leveraging these:
- Caching: Use Redis for route, view, configuration, and application caching.
- Database Query Optimization: Eager load relationships (
with()), useselect()to fetch only necessary columns, and optimize SQL queries. - Queueing Jobs: Offload time-consuming tasks (email sending, image processing) to background queues (e.g., using Redis as a queue driver).
- Opcode Caching: Ensure OPcache is enabled and configured correctly for PHP.
- Asset Bundling: Use Laravel Mix or Vite to compile and minify CSS/JS assets.
By combining robust infrastructure tuning with smart application development practices, you can achieve a highly performant and scalable Laravel application on OVH.