The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on OVH for PHP
Optimizing Nginx as a Reverse Proxy for PHP Applications
When deploying PHP applications, particularly those leveraging modern frameworks and asynchronous task processing, Nginx serves as an indispensable reverse proxy. Its efficiency in handling static assets and buffering client requests is paramount. This section details critical Nginx tuning parameters for a high-performance PHP stack on OVH infrastructure.
Nginx Configuration Tuning
The primary configuration file for Nginx is typically located at /etc/nginx/nginx.conf. We’ll focus on tuning the http block and individual server blocks.
Global HTTP Settings
Within the http block, several directives significantly impact performance and stability:
worker_processes: Set this to the number of CPU cores available. On OVH instances, this can be determined usingnprocorlscpu.worker_connections: This defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is1024or higher, depending on expected load. The system-wide limit (ulimit -n) must be higher thanworker_processes * worker_connections.multi_accept: Setting this toonallows workers to accept as many new connections as possible in one go.keepalive_timeout: Reduces the overhead of establishing new TCP connections. A value like65seconds is often suitable.send_timeout: Controls how long Nginx will wait for a response from the client before closing the connection. A value of10seconds is a reasonable default.client_body_buffer_size: Important for handling POST requests. A value of128kor256kis often sufficient.client_header_buffer_size: For client headers.1kis usually fine, but2kcan be safer for complex headers.large_client_header_buffers: For large client headers.4 8kis a common setting.open_file_cache: Caches file descriptors and metadata, reducing disk I/O.
Here’s an example snippet for the http block:
http {
# Determine worker_processes dynamically or set based on CPU cores
# Example: worker_processes auto; or worker_processes 4;
worker_processes 4;
worker_connections 4096; # Ensure ulimit -n is set higher
multi_accept on;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
client_body_buffer_size 256k;
client_header_buffer_size 2k;
large_client_header_buffers 4 8k;
# Enable open file cache for performance
open_file_cache max=2000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Include mime types and other configurations
include /etc/nginx/mime.types;
default_type application/octet-stream;
# ... other http configurations
}
Server Block Configuration for PHP-FPM
For PHP applications, Nginx acts as a proxy to PHP-FPM. The location ~ \.php$ block is critical. Ensure it’s configured for optimal communication with your PHP-FPM pool.
server {
listen 80;
server_name your_domain.com www.your_domain.com;
root /var/www/your_app/public;
index index.php index.html index.htm;
# Deny access to hidden files
location ~ /\.ht {
deny all;
}
# Pass PHP scripts to PHP-FPM
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Use the correct socket or IP:Port for your PHP-FPM pool
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Or 127.0.0.1:9000
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# FastCGI buffer tuning
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_read_timeout 300s; # Adjust based on long-running scripts
}
# Serve static files directly
location ~* \.(css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# Other configurations (e.g., SSL, error pages)
# ...
}
Nginx Performance Checks
After applying changes, test your Nginx configuration and reload the service:
sudo nginx -t sudo systemctl reload nginx
Monitor Nginx access and error logs (/var/log/nginx/access.log, /var/log/nginx/error.log) for any issues. Tools like ab (ApacheBench) or wrk can be used for load testing.
Tuning Gunicorn for PHP (via WSGI/ASGI Wrappers)
While Gunicorn is primarily a Python WSGI/ASGI HTTP Server, it can be used to serve PHP applications through wrappers like php-fpm-wsgi or similar solutions. This approach is less common than direct PHP-FPM but offers flexibility in environments where Python tooling is preferred. The tuning principles for Gunicorn itself remain relevant.
Gunicorn Configuration Parameters
Gunicorn’s performance is dictated by its worker processes, threads, and timeouts. A typical Gunicorn configuration file (e.g., gunicorn_config.py) might look like this:
# gunicorn_config.py import multiprocessing # Number of worker processes. A common recommendation is (2 * number_of_cores) + 1. workers = multiprocessing.cpu_count() * 2 + 1 # Worker class. 'sync' is the default and most basic. 'gevent' or 'eventlet' # can be used for asynchronous I/O if your application supports it. worker_class = 'sync' # or 'gevent', 'eventlet' # Number of threads per worker (only applicable for 'gthread' worker class) # threads = 2 # Bind to a socket or IP:Port. For Nginx proxying, a Unix socket is often preferred. # bind = "unix:/path/to/your/app.sock" bind = "127.0.0.1:8000" # Example for local binding # Maximum number of simultaneous connections per worker. # For 'sync' workers, this is typically 1. For 'eventlet'/'gevent', it can be much higher. # max_requests = 1000 # Restart workers after this many requests # max_requests_jitter = 50 # Jitter to spread restarts # Timeout for worker requests. # Adjust based on the longest expected request processing time. timeout = 120 # seconds # Logging configuration loglevel = 'info' accesslog = '-' # Log to stdout errorlog = '-' # Log to stderr # Other settings # pidfile = '/var/run/gunicorn.pid' # daemon = True # user = 'www-data' # group = 'www-data'
To run Gunicorn with this configuration:
gunicorn --config gunicorn_config.py your_wsgi_app:app
If using a PHP-WSGI wrapper, ensure the underlying PHP-FPM configuration is also optimized (see next section).
Tuning PHP-FPM for Optimal Performance
PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with web servers like Nginx. Its configuration directly impacts PHP execution speed and resource utilization.
PHP-FPM Configuration Files
The main configuration file is usually /etc/php/X.Y/fpm/php-fpm.conf, and pool configurations are in /etc/php/X.Y/fpm/pool.d/www.conf (or a custom pool name).
Global Settings (php-fpm.conf)
Key global settings include:
pid: Path to the PID file.error_log: Path to the error log.log_level: Logging verbosity (e.g.,notice,warning,error,debug).
Pool Settings (pool.d/www.conf)
This is where most tuning occurs. The pm (Process Manager) directive is crucial:
pm: Can bestatic,dynamic, orondemand.static: Keeps a fixed number of child processes running. Best for predictable high loads.dynamic: Starts processes as needed, up to a maximum. Good for variable loads.ondemand: Starts processes only when a request comes in and kills them after a timeout. Saves memory but can increase latency.
pm.max_children: The maximum number of child processes that will be spawned. This is the most critical setting for memory usage.pm.start_servers: Number of child processes to start when PHP-FPM starts.pm.min_spare_servers: Minimum number of idle processes.pm.max_spare_servers: Maximum number of idle processes.pm.process_idle_timeout: How long an idle process will live (used withdynamic).pm.max_requests: Maximum number of requests a child process will execute before respawning. Helps prevent memory leaks.listen: The socket or IP:Port PHP-FPM listens on (must match Nginx configuration).request_terminate_timeout: Maximum execution time for a single script.pm.child_init_timeout: Timeout for child process initialization.
Example pool.d/www.conf for a moderate-traffic server (adjust based on OVH instance RAM and CPU):
; /etc/php/7.4/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /var/run/php/php7.4-fpm.sock ; Or 127.0.0.1:9000 listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 50 ; Adjust based on available RAM. (Total RAM / Average Child RAM Usage) pm.start_servers = 5 pm.min_spare_servers = 2 pm.max_spare_servers = 10 pm.max_requests = 500 ; Prevents memory leaks over time request_terminate_timeout = 120s ; Long enough for most operations, but not infinite pm.child_init_timeout = 120s ; Other useful settings ; php_admin_value[memory_limit] = 256M ; php_admin_value[upload_max_filesize] = 64M ; php_admin_value[post_max_size] = 64M ; php_admin_value[max_execution_time] = 120
After modifying PHP-FPM configuration, reload the service:
sudo systemctl reload php7.4-fpm
Monitor PHP-FPM logs (e.g., /var/log/php7.4-fpm.log) and system resource usage (top, htop, free -m) to fine-tune pm.max_children and other parameters.
MySQL/MariaDB Performance Tuning on OVH
Database performance is often the bottleneck. Tuning MySQL/MariaDB involves adjusting configuration parameters and optimizing queries.
MySQL Configuration (my.cnf)
The main configuration file is typically /etc/mysql/my.cnf or located within /etc/mysql/conf.d/. Key parameters to adjust:
innodb_buffer_pool_size: The most critical setting for InnoDB. Should be 50-70% of available RAM on a dedicated database server. On a shared server, be more conservative.innodb_log_file_size: Larger log files can improve write performance but increase recovery time. A common starting point is256Mor512M.innodb_log_buffer_size: Larger buffer can improve performance for large transactions.16Mis often sufficient.max_connections: Maximum number of simultaneous client connections. Set based on application needs and server capacity.query_cache_sizeandquery_cache_type: Query cache is deprecated in MySQL 5.7 and removed in 8.0. If using older versions, tune carefully; it can cause contention. For modern versions, disable it.tmp_table_sizeandmax_heap_table_size: Affects the size of in-memory temporary tables.key_buffer_size: Important for MyISAM tables (less common now).sort_buffer_size,join_buffer_size,read_buffer_size,read_rnd_buffer_size: Per-session buffers. Tune cautiously as they can consume significant memory per connection.
Example my.cnf snippet (for a server with 8GB RAM, primarily a DB server):
[mysqld] # General Settings user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc_messages = en_US skip-external-locking # InnoDB Settings (Crucial for performance) default_storage_engine = InnoDB innodb_buffer_pool_size = 4G ; ~50% of 8GB RAM innodb_log_file_size = 512M innodb_log_buffer_size = 16M innodb_flush_log_at_trx_commit = 1 ; For ACID compliance, 2 for better performance with slight risk innodb_flush_method = O_DIRECT ; Recommended for Linux innodb_file_per_table = 1 ; Recommended for manageability # Connection Settings max_connections = 200 ; Adjust based on application needs # thread_cache_size = 16 ; Cache threads # Query Cache (Disable for MySQL 8.0+, tune cautiously for older versions) query_cache_type = 0 query_cache_size = 0 # Per-session Buffers (Tune cautiously) # sort_buffer_size = 2M # join_buffer_size = 2M # read_buffer_size = 1M # read_rnd_buffer_size = 2M # MyISAM Settings (if applicable) # key_buffer_size = 128M # Logging log_error = /var/log/mysql/error.log slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 2 ; Log queries longer than 2 seconds log_queries_not_using_indexes = 1 ; Log queries that don't use indexes
After changes, restart MySQL:
sudo systemctl restart mysql
Query Optimization and Indexing
Even with perfect server tuning, inefficient queries will cripple performance. Regularly analyze slow query logs and use EXPLAIN to understand query execution plans.
-- Example of using EXPLAIN EXPLAIN SELECT * FROM users WHERE email = '[email protected]'; -- Add an index if 'email' column is frequently queried CREATE INDEX idx_users_email ON users (email);
OVH provides tools for monitoring database performance within their control panel, which should be leveraged to identify problematic queries and resource bottlenecks.
Putting It All Together: Monitoring and Iteration
Performance tuning is an iterative process. Implement changes incrementally and monitor their impact. Key metrics to track include:
- Server CPU and RAM usage (
top,htop,vmstat) - Nginx request rates, error rates, and response times (using
ngx_http_stub_status_moduleor external monitoring tools) - PHP-FPM process count and queue length
- MySQL connection usage, query latency, and buffer pool hit rate
- Application-specific metrics (e.g., response times for key API endpoints)
Utilize tools like Prometheus, Grafana, Datadog, or New Relic for comprehensive monitoring. On OVH, their built-in monitoring dashboards offer a good starting point.
Remember to always back up your configuration files before making changes and test thoroughly in a staging environment before deploying to production.