The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on OVH for WooCommerce
Nginx Configuration for High-Traffic WooCommerce
Optimizing Nginx is paramount for serving static assets efficiently and acting as a robust reverse proxy for your WooCommerce application. For OVH environments, especially those with dedicated resources, fine-tuning Nginx can significantly reduce latency and improve throughput.
We’ll focus on key directives that impact performance and stability. This configuration assumes a typical setup where Nginx handles SSL termination, serves static files, and forwards dynamic requests to a backend application server (Gunicorn for Python/Django/Flask or PHP-FPM for PHP).
Core Nginx Performance Directives
The nginx.conf file, typically located at /etc/nginx/nginx.conf or within /etc/nginx/conf.d/, is our starting point. We’ll adjust worker processes, connections, and caching mechanisms.
Worker Processes and Connections
The worker_processes directive should ideally be set to the number of CPU cores available on your server. For OVH dedicated servers, this is easily identifiable. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024 or higher, depending on your expected load.
Tuning Keep-Alive and Buffers
keepalive_timeout controls how long an idle connection will remain open. A moderate value (e.g., 65 seconds) can reduce the overhead of establishing new TCP connections. client_body_buffer_size and client_header_buffer_size are crucial for handling large requests and headers. For WooCommerce, which can involve large product images and AJAX requests, increasing these might be necessary.
Enabling Gzip Compression
Gzip compression significantly reduces the size of text-based assets (HTML, CSS, JS, JSON), leading to faster load times. Ensure it’s enabled and configured appropriately.
Example Nginx Configuration Snippet
This snippet demonstrates key optimizations within the http block. Adapt worker_processes to your server’s core count.
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto; # Set to number of CPU cores for dedicated servers
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 4096; # Adjust based on 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
# Buffers for large requests
client_body_buffer_size 10M; # Adjust as needed
client_header_buffer_size 1M; # Adjust as needed
large_client_header_buffers 4 1M; # Adjust as needed
# Gzip Compression
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # Compression level (1-9)
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# SSL Configuration (example for a site)
# include /etc/nginx/sites-enabled/*; # Or define directly
# Proxying to backend (e.g., Gunicorn or PHP-FPM)
# proxy_pass http://unix:/path/to/your/app.sock; # For Gunicorn/uWSGI
# proxy_pass http://127.0.0.1:9000; # For PHP-FPM
# Other directives like caching, rate limiting, etc.
}
WooCommerce Specific Nginx Optimizations
For WooCommerce, we need to consider caching static assets aggressively and potentially setting up specific rules for media files.
Static Asset Caching
Leverage browser caching for static assets like CSS, JavaScript, and images. This reduces server load and speeds up repeat visits.
# Inside your server block or http block
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 365d; # Cache for 1 year
add_header Cache-Control "public, no-transform";
access_log off; # Optionally disable logging for static assets
log_not_found off;
}
# Cache fonts
location ~* \.(woff|woff2|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, no-transform";
access_log off;
log_not_found off;
}
Disabling Access Logging for Static Files
For high-traffic sites, the access log can become a bottleneck. Disabling it for static assets can improve performance.
# Within your static asset location blocks: access_log off; log_not_found off;
Gunicorn Tuning for Python-based WooCommerce (e.g., Django/Wagtail)
When using Python frameworks like Django or Flask for a WooCommerce backend, Gunicorn is a popular choice for serving the application. Proper Gunicorn configuration is crucial for handling concurrent requests and managing worker processes effectively.
Gunicorn Worker Types and Scaling
Gunicorn supports several worker types. For most I/O-bound web applications, the gevent or event worker classes are recommended. The number of workers is a critical tuning parameter. A common heuristic is (2 * number_of_cores) + 1, but this should be adjusted based on your application’s behavior and server resources.
Worker Timeout and Max Requests
--timeout specifies the maximum time a worker can spend on a request before being restarted. For long-running operations (e.g., complex report generation, large data imports), this might need to be increased. However, excessively high timeouts can mask performance issues. --max-requests is a vital directive for preventing memory leaks by restarting workers after a certain number of requests. This is a proactive measure against gradual performance degradation.
Gunicorn Configuration Example
This example shows how to launch Gunicorn with optimized settings, typically from a systemd service file or a shell script.
# Example command to start Gunicorn
# Assuming your Django app is in a directory 'myproject' and the WSGI application is 'myproject.wsgi:application'
gunicorn --workers 4 \
--worker-class gevent \
--bind unix:/path/to/your/app.sock \
--timeout 120 \
--max-requests 5000 \
--log-level info \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
myproject.wsgi:application
Explanation of Directives:
--workers 4: Adjust based on your CPU cores. For a 4-core server, 5 workers (2*4 + 1) might be a good starting point.--worker-class gevent: Uses gevent for asynchronous I/O, suitable for I/O-bound tasks common in web applications.--bind unix:/path/to/your/app.sock: Binds Gunicorn to a Unix socket, which is generally faster than TCP/IP for local communication between Nginx and Gunicorn.--timeout 120: Sets a 120-second timeout for worker requests. Tune this carefully.--max-requests 5000: Restarts workers after 5000 requests to mitigate memory leaks.--log-level info: Sets the logging level.--access-logfile&--error-logfile: Specifies log file locations. Ensure the directories exist and are writable by the Gunicorn user.
Systemd Service File for Gunicorn
Managing Gunicorn with systemd ensures it runs as a service, starts on boot, and restarts automatically if it crashes.
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=your_app_user
Group=www-data
WorkingDirectory=/path/to/your/project
ExecStart=/path/to/your/venv/bin/gunicorn \
--workers 4 \
--worker-class gevent \
--bind unix:/path/to/your/app.sock \
--timeout 120 \
--max-requests 5000 \
--log-level info \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
myproject.wsgi:application
[Install]
WantedBy=multi-user.target
After creating this file, run: sudo systemctl daemon-reload, sudo systemctl start gunicorn, and sudo systemctl enable gunicorn.
PHP-FPM Tuning for PHP-based WooCommerce
For traditional PHP-based WooCommerce installations, PHP-FPM (FastCGI Process Manager) is the standard. Optimizing its configuration is critical for handling concurrent PHP requests efficiently.
PHP-FPM Process Manager Settings
The pm (Process Manager) setting determines how PHP-FPM manages worker processes. The most common options are static, dynamic, and ondemand.
static: Pre-forks a fixed number of child processes. Good for predictable loads and dedicated servers where you can precisely allocate resources.dynamic: Starts a few children initially and spawns more as needed, up to a defined maximum. It also kills idle children to save resources. This is often a good balance.ondemand: Starts no children initially and spawns them only when a request comes in. Can save resources but might introduce slight latency on the first request.
For OVH dedicated servers, static or dynamic with carefully tuned parameters are usually preferred for consistent performance.
Key PHP-FPM Directives
These directives are found in the PHP-FPM pool configuration file, typically located at /etc/php/[version]/fpm/pool.d/www.conf.
; /etc/php/[version]/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /run/php/php[version]-fpm.sock ; Or TCP/IP: listen = 127.0.0.1:9000 listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 100 ; Max number of children at any point. Adjust based on RAM. pm.start_servers = 10 ; Number of children to start when PHP-FPM starts. pm.min_spare_servers = 5 ; Min number of idle servers. pm.max_spare_servers = 20 ; Max number of idle servers. pm.process_idle_timeout = 10s ; Timeout for killing idle processes (if pm.max_spare_servers is reached). pm.max_requests = 5000 ; Max requests per child process before respawning. Crucial for memory leak prevention. request_terminate_timeout = 120s ; Max execution time for a single script. request_slowlog_timeout = 30s ; Log scripts that take longer than this. ; Other settings catch_workers_output = yes ;slowlog = /var/log/php/php-fpm-slow.log
Tuning Considerations:
pm.max_children: This is the most critical setting. It should be calculated based on your server’s available RAM. A common formula is:(Total RAM - RAM used by OS/other services) / Average RAM per PHP process. Monitor memory usage closely.pm.max_requests: Similar to Gunicorn’s--max-requests, this prevents memory leaks by recycling worker processes.request_terminate_timeout: Adjust based on the longest expected execution time for your WooCommerce operations.request_slowlog_timeout: Enable and monitor the slow log to identify performance bottlenecks in your PHP code.
After modifying the PHP-FPM configuration, restart the service: sudo systemctl restart php[version]-fpm.
PostgreSQL Tuning for WooCommerce Databases
The PostgreSQL database is the backbone of WooCommerce. Optimizing its performance, particularly for read/write operations and query execution, is essential for a responsive store.
Key PostgreSQL Configuration Parameters
The primary configuration file is postgresql.conf, typically found in /etc/postgresql/[version]/main/.
Memory Allocation
shared_buffers: This is the most important parameter. It defines the amount of memory PostgreSQL uses for caching data. A common recommendation is 25% of system RAM, but for dedicated database servers, it can be pushed higher (up to 40-50% if the OS has enough memory for its own caching). For a 64GB RAM server, 16GB (16384MB) is a good starting point.
work_mem: Memory used for internal sort operations and hash tables. It’s allocated per sort/hash operation within a query. Setting this too high can lead to excessive memory usage if many complex queries run concurrently. Start conservatively (e.g., 16MB-64MB) and increase if `EXPLAIN ANALYZE` shows disk-based sorts.
maintenance_work_mem: Memory used for maintenance tasks like `VACUUM`, `CREATE INDEX`, and `ALTER TABLE`. A larger value can significantly speed up these operations. 256MB to 1GB is often suitable.
Checkpointing and WAL (Write-Ahead Logging)
max_wal_size and min_wal_size: These control the amount of WAL data that can accumulate before a checkpoint is triggered. Increasing these values reduces the frequency of checkpoints, which can improve write performance by reducing I/O spikes. However, it also increases recovery time after a crash.
checkpoint_completion_target: Spreads the checkpoint process over time, reducing I/O impact. A value of 0.9 is often recommended.
Example postgresql.conf Snippet
# /etc/postgresql/[version]/main/postgresql.conf # General Settings listen_addresses = '*' # Or specific IPs for security port = 5432 # Memory Settings (Example for a 64GB RAM server) shared_buffers = 16GB work_mem = 32MB maintenance_work_mem = 512MB # Checkpointing & WAL max_wal_size = 4GB min_wal_size = 1GB checkpoint_completion_target = 0.9 wal_buffers = 16MB # Typically 1/3 of shared_buffers, up to 16MB # Connection Settings max_connections = 200 # Adjust based on application needs and server resources superuser_reserved_connections = 3 # Query Planner effective_cache_size = 48GB # Roughly 75% of total RAM, assuming OS cache is also used # Logging log_destination = 'stderr' logging_collector = on log_directory = 'pg_log' log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' log_min_duration_statement = 250ms # Log queries taking longer than 250ms log_checkpoints = on log_connections = on log_disconnections = on log_lock_waits = on log_temp_files = on log_autovacuum_min_duration = 0 # Log all autovacuum activity
After modifying postgresql.conf, restart the PostgreSQL service: sudo systemctl restart postgresql.
Database Indexing and Vacuuming
Beyond configuration, proper indexing and regular maintenance are critical.
Essential Indexes for WooCommerce
WooCommerce relies heavily on the WordPress database structure. Ensure critical tables like wp_posts, wp_postmeta, wp_options, and wp_wc_order_stats have appropriate indexes. WordPress often creates basic indexes, but custom plugins or heavy usage might require more specific ones. Use EXPLAIN ANALYZE on slow queries to identify missing indexes.
-- Example: Checking indexes on wp_posts
SELECT
tablename,
indexname,
indexdef
FROM
pg_indexes
WHERE
tablename = 'wp_posts';
-- Example: Creating a potential index (use with caution and after analysis)
-- CREATE INDEX idx_posts_post_type_date ON wp_posts (post_type, post_date);
Autovacuum Tuning
PostgreSQL’s autovacuum process is essential for reclaiming space from dead tuples and preventing table bloat. Ensure it’s enabled and tuned. The parameters in postgresql.conf (like autovacuum_vacuum_threshold, autovacuum_analyze_threshold, autovacuum_vacuum_scale_factor, autovacuum_analyze_scale_factor) control when vacuuming occurs. For busy WooCommerce databases, you might need to lower the scale factors or increase maintenance_work_mem.
Regularly monitor table bloat using queries like:
SELECT
relname,
n_live_tup,
n_dead_tup,
pg_size_pretty(pg_table_size(oid)) AS table_size,
pg_size_pretty(pg_total_relation_size(oid)) AS total_size
FROM
pg_stat_user_tables
WHERE
n_dead_tup > 0
ORDER BY
n_dead_tup DESC;
If bloat is significant, consider running manual `VACUUM FULL` (which locks tables) or `VACUUM` (non-blocking but less effective at reclaiming space) during maintenance windows, or further tune autovacuum parameters.
Monitoring and Iterative Tuning
Performance tuning is not a one-time task. Continuous monitoring and iterative adjustments are key. Utilize tools like:
- Nginx:
nginx -s reloadfor applying config changes. Check/var/log/nginx/access.loganderror.log. Usengx_http_stub_status_modulefor basic metrics. - Gunicorn/PHP-FPM: System logs (
journalctl -u gunicornorjournalctl -u php[version]-fpm). Monitor process counts and memory usage viahtoportop. - PostgreSQL:
pg_stat_activityfor current queries,pg_stat_statementsextension for query performance analysis,pg_buffercachefor buffer usage, and OS-level tools likeiostatandvmstat. - Application Level: WooCommerce’s own performance tools, New Relic, Datadog, or similar APM solutions.
Start with conservative settings, monitor the impact, and gradually increase parameters like worker counts, buffer sizes, and timeouts as needed, always validating against your specific workload and server capabilities on OVH.