The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on OVH for C
Nginx Configuration for High Throughput
Optimizing Nginx for a high-traffic environment on OVH requires a multi-faceted approach, focusing on connection handling, caching, and efficient static file serving. We’ll start with the core `nginx.conf` directives.
Worker Processes and Connections
The `worker_processes` directive should ideally be set to the number of CPU cores available on your server. This allows Nginx to utilize all available processing power for handling requests. The `worker_connections` directive defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024, but this can be increased significantly depending on your server’s RAM and the nature of your application’s traffic (e.g., long-polling vs. short requests).
worker_processes auto; # Or set to the number of CPU cores
events {
worker_connections 4096; # Adjust based on RAM and traffic patterns
multi_accept on;
use epoll; # For Linux systems
}
Keepalive Connections
Enabling keepalive connections reduces the overhead of establishing new TCP connections for each HTTP request. The `keepalive_timeout` controls how long an idle keepalive connection will remain open. A value between 60 and 120 seconds is often a good balance, preventing resource exhaustion while still offering performance benefits. `keepalive_requests` limits the number of requests that can be served over a single keepalive connection.
http {
# ... other http directives ...
keepalive_timeout 75;
keepalive_requests 1000;
# ... server blocks ...
}
Gzip Compression
Gzip compression can significantly reduce the bandwidth required to transfer assets, leading to faster page load times. Ensure it’s enabled and configured appropriately for your content types. The `gzip_comp_level` (1-9) determines the compression ratio; a level of 4-6 is often a good compromise between compression effectiveness and CPU usage.
http {
# ... other http directives ...
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_disable "msie6"; # Optional: disable for older IE versions
}
Client Body and Header Buffers
For applications that handle larger file uploads or complex POST requests, tuning `client_body_buffer_size` and `client_max_body_size` is crucial. Similarly, `large_client_header_buffers` can prevent errors when dealing with requests that have many headers.
http {
# ... other http directives ...
client_body_buffer_size 128k;
client_max_body_size 50m; # Adjust as per your application's needs
large_client_header_buffers 4 128k;
}
Gunicorn Tuning for Python Applications
When deploying Python web applications (e.g., Flask, Django) on OVH, Gunicorn is a popular choice for serving requests. Its performance is heavily influenced by the number of worker processes and their type.
Worker Processes and Type
Gunicorn’s `workers` setting determines the number of worker processes that will handle requests. A common recommendation is `(2 * number_of_cores) + 1`. The `worker_class` is also critical. For I/O-bound applications, `gevent` or `eventlet` (asynchronous workers) can significantly improve concurrency by allowing workers to handle multiple requests simultaneously without blocking. For CPU-bound tasks, the default `sync` worker class might be sufficient, but it’s less efficient for high concurrency.
# Example command to start Gunicorn with gevent workers gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application
If you’re using `gevent` or `eventlet`, ensure you have installed the respective libraries:
pip install gevent
Worker Timeout and Graceful Reloads
The `timeout` setting specifies the number of seconds Gunicorn will wait for a worker to respond before considering it dead. Setting this too low can lead to premature worker restarts for legitimate long-running requests. `graceful_timeout` is used during reloads to allow existing requests to complete.
gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 --timeout 60 --graceful-timeout 60 myapp.wsgi:application
Access and Error Logging
Proper logging is essential for debugging and performance monitoring. Gunicorn can log to stdout/stderr (useful for containerized environments) or to specific files. Configuring log levels and rotation is important for production systems.
gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
--log-level info \
myapp.wsgi:application
For log rotation, consider using `logrotate` on your OVH instance. A typical `logrotate` configuration for Gunicorn might look like this:
/var/log/gunicorn/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload gunicorn.service > /dev/null 2>&1 || true
endscript
}
PHP-FPM Tuning for PHP Applications
For PHP applications served via Nginx (e.g., WordPress, Laravel), PHP-FPM is the standard FastCGI Process Manager. Tuning its pool configuration is critical for performance and stability.
Process Manager Settings
PHP-FPM offers three primary process management strategies: `static`, `dynamic`, and `ondemand`. For predictable high-traffic loads, `static` is often preferred as it pre-forks a fixed number of workers, reducing latency. `dynamic` is a good compromise, spawning workers as needed up to a maximum. `ondemand` is resource-efficient but can introduce latency on initial requests.
; /etc/php/8.1/fpm/pool.d/www.conf (example for PHP 8.1) [www] user = www-data group = www-data listen = /run/php/php8.1-fpm.sock ; Process Management (choose one strategy) ; pm = static ; pm.max_children = 50 ; For static, this is the fixed number of workers pm = dynamic pm.max_children = 100 ; Max number of children at any one time pm.start_servers = 10 ; Number of servers started on boot pm.min_spare_servers = 5 ; Minimum number of idle servers pm.max_spare_servers = 20 ; Maximum number of idle servers ; pm = ondemand ; pm.max_children = 50 ; pm.process_idle_timeout = 10s
The values for `pm.max_children`, `pm.start_servers`, etc., should be tuned based on your server’s RAM and the typical load. A common starting point for `pm.max_children` is to divide your available RAM (in MB) by the average memory footprint of a PHP-FPM worker process. Monitor your system’s memory usage closely.
Request Termination and Slowlog
To prevent runaway scripts from consuming resources indefinitely, configure `request_terminate_timeout`. The `slowlog` directive is invaluable for identifying performance bottlenecks within your PHP code.
; /etc/php/8.1/fpm/pool.d/www.conf [www] ; ... other settings ... request_terminate_timeout = 60s ; Terminate scripts after 60 seconds ; Enable slowlog for debugging request_slowlog_timeout = 10s slowlog = /var/log/php/php-fpm-slow.log
Ensure the log directory (`/var/log/php/`) is writable by the PHP-FPM user and that log rotation is configured for these files as well.
Environment Variables and PHP Settings
You can pass environment variables to PHP-FPM processes, which can be useful for application configuration. Additionally, core PHP settings like `memory_limit` and `max_execution_time` can be adjusted within the FPM pool configuration.
; /etc/php/8.1/fpm/pool.d/www.conf [www] ; ... other settings ... ; Environment variables env[MY_APP_ENV] = production env[DATABASE_URL] = postgresql://user:pass@host:port/dbname ; PHP settings php_admin_value[memory_limit] = 256M php_admin_value[max_execution_time] = 120 php_admin_value[upload_max_filesize] = 64M php_admin_value[post_max_size] = 64M
PostgreSQL Performance Tuning on OVH
Optimizing PostgreSQL on OVH involves tuning its configuration parameters, particularly those related to memory, I/O, and query execution. The primary configuration file is `postgresql.conf`.
Shared Buffers and WAL
`shared_buffers` is arguably the most critical parameter. It defines the amount of memory PostgreSQL uses for caching data. A common recommendation is 25% of your server’s total RAM, but this can be adjusted upwards if your server is dedicated to PostgreSQL and has ample RAM. `wal_buffers` is for Write-Ahead Log buffering; a value of 16MB is often sufficient. `wal_writer_delay` controls how often the WAL writer flushes buffers to disk.
# postgresql.conf shared_buffers = 4GB ; Example for a server with 16GB RAM wal_buffers = 16MB wal_writer_delay = 200ms ; Default is 200ms, can be tuned checkpoint_completion_target = 0.9 ; Spread checkpoints over time checkpoint_timeout = 5min ; How often checkpoints occur
Work Memory and Maintenance Work Memory
`work_mem` controls the amount of memory used by internal sort operations and hash tables before writing to temporary disk files. It’s allocated per sort/hash operation, so setting it too high can lead to memory exhaustion if many such operations occur concurrently. `maintenance_work_mem` is used for maintenance operations like `VACUUM`, `CREATE INDEX`, and `ALTER TABLE`.
# postgresql.conf work_mem = 64MB ; Adjust based on query complexity and concurrency maintenance_work_mem = 512MB ; Can be set higher than work_mem
Effective Cache Size
`effective_cache_size` is a PostgreSQL configuration parameter that informs the query planner about the total amount of memory available for disk caching by the operating system and PostgreSQL’s shared buffers. It doesn’t allocate memory itself but influences the planner’s decision on whether to use index scans. A good starting point is 50-75% of the total system RAM.
# postgresql.conf effective_cache_size = 12GB ; Example for a server with 16GB RAM
Connection Pooling and Max Connections
Managing database connections efficiently is vital. `max_connections` defines the maximum number of concurrent connections. Ensure this is set high enough to accommodate your application’s needs but not so high that it exhausts server memory. For applications with many short-lived connections, using a connection pooler like PgBouncer is highly recommended. This allows PostgreSQL to maintain a smaller `max_connections` value.
# postgresql.conf max_connections = 200 ; Adjust based on RAM and application needs ; Consider using PgBouncer for connection pooling
Autovacuum Tuning
Autovacuum is essential for reclaiming space occupied by dead tuples and preventing transaction ID wraparound. Tuning its parameters can significantly impact performance, especially on busy databases.
# postgresql.conf autovacuum = on log_autovacuum_min_duration = 1s ; Log autovacuum actions taking longer than 1 second autovacuum_max_workers = 3 ; Number of autovacuum worker processes autovacuum_naptime = 15s ; How often to check for jobs autovacuum_vacuum_threshold = 50 ; Minimum number of rows to vacuum autovacuum_analyze_threshold = 50 ; Minimum number of rows to analyze autovacuum_vacuum_scale_factor = 0.1 ; Fraction of table size to vacuum autovacuum_analyze_scale_factor = 0.05 ; Fraction of table size to analyze
For very large or very active tables, you might need to adjust `autovacuum_vacuum_scale_factor` and `autovacuum_analyze_scale_factor` to lower values or even set them to 0 and rely on `autovacuum_vacuum_threshold` and `autovacuum_analyze_threshold` for specific tables.
Monitoring and Diagnostics
Regular monitoring is key to identifying performance regressions. Use tools like `pg_stat_statements` to find slow queries, `htop` or `top` to monitor resource usage, and Nginx/PHP-FPM logs for application-level issues. For PostgreSQL, `pg_stat_activity` provides real-time information about active queries and connections.
-- Enable pg_stat_statements extension
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- View top 10 slowest queries by total execution time
SELECT
query,
calls,
total_exec_time,
rows
FROM
pg_stat_statements
ORDER BY
total_exec_time DESC
LIMIT 10;
By systematically tuning these components – Nginx for network efficiency, Gunicorn/PHP-FPM for application serving, and PostgreSQL for data management – you can build a robust and high-performing infrastructure on OVH.