The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on Linode for WooCommerce
Nginx Configuration for WooCommerce High Traffic
Optimizing Nginx is paramount for serving static assets, handling SSL termination, and acting as a reverse proxy for your PHP application server. For a WooCommerce site, efficient static file serving and robust connection management are key.
We’ll focus on tuning worker_processes, worker_connections, and enabling Gzip compression. For a Linode instance, setting worker_processes to the number of CPU cores is a good starting point. worker_connections dictates how many simultaneous connections each worker process can handle; a value of 1024 or higher is typical for production.
Core Nginx Tuning Directives
Edit your nginx.conf file, typically located at /etc/nginx/nginx.conf or within /etc/nginx/conf.d/. Ensure these directives are set within the http block.
# Determine the number of CPU cores available
# For example, if you have 4 cores, set worker_processes to 4
worker_processes 4;
events {
# Maximum number of simultaneous connections per worker process
worker_connections 4096;
# Use epoll for Linux, kqueue for BSD/macOS
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
# Enable Gzip compression for text-based assets
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;
# Keepalive timeout for persistent connections
keepalive_timeout 65;
# Enable sendfile for efficient file transfer
sendfile on;
# Adjust tcp_nopush and tcp_nodelay for performance
tcp_nopush on;
tcp_nodelay on;
# Buffering settings
client_body_buffer_size 10K;
client_max_body_size 20m; # Adjust based on WooCommerce upload needs
client_header_buffer_size 1K;
large_client_header_buffers 4 8K;
# Timeout settings
client_header_timeout 10;
client_body_timeout 10;
send_timeout 10;
lingering_close off;
lingering_time 30;
# Server block for your WooCommerce site
server {
listen 80;
server_name your_domain.com www.your_domain.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name your_domain.com www.your_domain.com;
# SSL Configuration (replace with your actual cert paths)
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# Static file caching
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# Proxy to your PHP application server (Gunicorn/FPM)
location / {
proxy_pass http://unix:/var/run/php/php7.4-fpm.sock; # Or your Gunicorn socket
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s; # Increase timeout for potentially long WooCommerce requests
proxy_connect_timeout 75s;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
}
After modifying nginx.conf, always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Gunicorn/PHP-FPM Tuning for WooCommerce
The choice between Gunicorn (for Python/Django/Flask) and PHP-FPM (for PHP/WordPress/WooCommerce) dictates the tuning strategy. We’ll cover both.
PHP-FPM Tuning (for WordPress/WooCommerce)
PHP-FPM’s performance is heavily influenced by its process manager settings. The pm (process manager) can be set to dynamic, static, or ondemand. For a busy WooCommerce site, dynamic or static are generally preferred.
Edit your PHP-FPM pool configuration file, typically found at /etc/php/X.Y/fpm/pool.d/www.conf (replace X.Y with your PHP version, e.g., 7.4).
; Example for pm = dynamic pm = dynamic pm.max_children = 100 ; Max number of children serving requests pm.start_servers = 10 ; Number of children when pool starts pm.min_spare_servers = 5 ; Min number of idle resp. to keep pm.max_spare_servers = 20 ; Max number of idle resp. to keep pm.process_idle_timeout = 10s ; Timeout for idle processes ; Example for pm = static ; pm = static ; pm.max_children = 150 ; Fixed number of children ; Adjust request_terminate_timeout for long-running WooCommerce tasks request_terminate_timeout = 300s ; Adjust listen socket (ensure it matches Nginx config) listen = /var/run/php/php7.4-fpm.sock ; listen.owner = www-data ; listen.group = www-data ; listen.mode = 0660 ; Adjust memory limit if necessary for heavy plugins/themes ; memory_limit = 256M
After changes, reload PHP-FPM:
sudo systemctl reload php7.4-fpm
Gunicorn Tuning (for Python Apps)
Gunicorn’s worker count and type are critical. For CPU-bound tasks, use sync workers. For I/O-bound tasks, gevent or event workers are more efficient. A common starting point for workers is (2 * number_of_cores) + 1.
You can configure Gunicorn via command-line arguments or a configuration file.
# Example command-line invocation
gunicorn --workers 5 \
--worker-class gevent \
--bind unix:/path/to/your/app.sock \
--timeout 300 \
your_app.wsgi:application
Or, create a gunicorn.conf.py file:
import multiprocessing bind = "unix:/path/to/your/app.sock" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "gevent" # or "sync", "event" timeout = 300 loglevel = "info" accesslog = "/var/log/gunicorn/access.log" errorlog = "/var/log/gunicorn/error.log"
Ensure your Nginx proxy_pass directive points to the correct Gunicorn socket path.
PostgreSQL Performance Tuning for WooCommerce
PostgreSQL is the backbone of WooCommerce data. Optimizing its configuration, particularly memory allocation and query planning, can yield significant performance gains.
Key PostgreSQL Configuration Parameters
The primary configuration file is postgresql.conf, usually located in /etc/postgresql/X.Y/main/. Always back up this file before making changes.
# Shared Memory Buffers # Aim for 15-25% of total system RAM. For a 4GB Linode, start with 1GB. shared_buffers = 1GB # Write-Ahead Log (WAL) Buffers # Typically 1/4 of shared_buffers, but not excessively large. wal_buffers = 16MB # Effective Cache Size # Estimate of how much memory is available for disk caching by the OS and PostgreSQL. # Set to 50-75% of total RAM. effective_cache_size = 3GB # Maintenance Work Memory # Used for VACUUM, CREATE INDEX, ALTER TABLE. Larger values speed up maintenance. maintenance_work_mem = 256MB # Work Memory # Used for sorting and hashing operations. Adjust based on query complexity. # Start with a moderate value and monitor. work_mem = 16MB # Checkpointing # Controls how often WAL is flushed to disk. More frequent checkpoints reduce recovery time but increase I/O. max_wal_size = 1GB min_wal_size = 128MB checkpoint_completion_target = 0.9 # Connection Pooling (if using pgBouncer or similar) # max_connections = 100 # Adjust based on your application's needs and server resources # Autovacuum settings (crucial for preventing table bloat) autovacuum = on autovacuum_max_workers = 3 autovacuum_naptime = 15s autovacuum_vacuum_threshold = 50 autovacuum_analyze_threshold = 50 autovacuum_vacuum_scale_factor = 0.1 autovacuum_analyze_scale_factor = 0.05 # Logging (essential for diagnostics) log_destination = 'stderr' logging_collector = on log_directory = 'pg_log' log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' log_min_duration_statement = 1000 # Log statements longer than 1 second log_checkpoints = on log_connections = on log_disconnections = on log_lock_waits = on log_temp_files = 0 # Log temp files larger than 0 bytes log_autovacuum_min_duration = 0 # Log all autovacuum actions
After modifying postgresql.conf, restart the PostgreSQL service:
sudo systemctl restart postgresql
Query Optimization and Indexing
Regularly analyze slow queries using PostgreSQL’s logging. WooCommerce can generate complex queries, especially around product searches, order processing, and reporting.
Use EXPLAIN ANALYZE to understand query execution plans. Identify missing indexes or inefficient joins.
-- Example: Analyze a slow query EXPLAIN ANALYZE SELECT * FROM wp_posts WHERE post_type = 'product' AND post_status = 'publish'; -- Example: Add an index to speed up product queries CREATE INDEX idx_posts_type_status ON wp_posts (post_type, post_status); -- Example: Analyze order status queries EXPLAIN ANALYZE SELECT * FROM wp_posts WHERE post_type = 'shop_order' AND post_status = 'wc-processing'; -- Consider indexing custom fields or meta data if frequently queried -- CREATE INDEX idx_postmeta_key_value ON wp_postmeta (meta_key, meta_value); -- Use with caution, can be large
For WooCommerce, specific tables like wp_posts, wp_postmeta, wp_options, and order-related tables are critical. Monitor their growth and fragmentation.
System-Level Monitoring and Diagnostics
Effective monitoring is crucial for identifying bottlenecks and validating tuning efforts. Use tools like htop, iotop, netstat, and PostgreSQL’s built-in statistics.
Key Monitoring Commands
- CPU Usage:
htoportop. Look for consistently high CPU usage on Nginx workers, PHP-FPM processes, or PostgreSQL. - Memory Usage:
htoporfree -h. Monitor for swapping, which indicates insufficient RAM. - Disk I/O:
iotop(requires root). Identify processes heavily utilizing disk, often PostgreSQL or Nginx if caching is misconfigured. - Network Connections:
netstat -tulnp. Check listening ports and active connections. - Nginx Status: Enable the
stub_statusmodule in Nginx to monitor active connections, requests, and read/write rates.
# Add to your Nginx http block or a specific server block
location /nginx_status {
stub_status;
allow 127.0.0.1; # Restrict access
deny all;
}
Access this status page via http://your_domain.com/nginx_status (if allowed). You’ll see metrics like Active connections, accepts, handled, and requests.
PostgreSQL Statistics:
-- Check active queries
SELECT pid, age(clock_timestamp(), query_start), usename, query
FROM pg_stat_activity
WHERE state != 'idle' AND query NOT LIKE '%pg_stat_activity%'
ORDER BY query_start;
-- Check database cache hit ratio
SELECT
datname,
pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(datname)) AS size,
blks_read,
blks_hit,
CASE WHEN blks_hit + blks_read = 0 THEN 0 ELSE round(cr.blks_hit / (cr.blks_hit + cr.blks_read) * 100.0) END AS hit_ratio
FROM pg_catalog.pg_database d
JOIN (
SELECT
refname,
sum(blks_read) as blks_read,
sum(blks_hit) as blks_hit
FROM pg_statio_user_tables
GROUP BY refname
) cr ON d.datname = cr.refname
ORDER BY hit_ratio DESC;
A cache hit ratio below 95% for your WooCommerce database might indicate insufficient shared_buffers or effective caching strategies.