The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on DigitalOcean for WordPress
Nginx as a High-Performance Frontend for WordPress
For WordPress deployments, Nginx excels as a reverse proxy and static file server. Its event-driven architecture handles high concurrency with minimal resource overhead. We’ll focus on tuning Nginx for optimal WordPress performance, particularly concerning caching and connection management.
Nginx Configuration for WordPress
The core of our Nginx tuning lies within its main configuration file (typically /etc/nginx/nginx.conf) and site-specific configurations (e.g., /etc/nginx/sites-available/your-wordpress-site). We’ll optimize worker processes, connection limits, and enable gzip compression.
Worker Processes and Connections
The worker_processes directive should ideally be set to the number of CPU cores available on your server. The worker_connections directive dictates the maximum number of simultaneous connections a worker process can handle. A common starting point is 1024, but this can be increased based on server load and RAM.
Example nginx.conf Snippet
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto; # Or set to the number of CPU cores
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 4096; # Increased from default 1024
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 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;
# SSL Configuration (if applicable)
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_prefer_server_ciphers on;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
WordPress-Specific Nginx Configuration
This section focuses on caching static assets, preventing hotlinking, and correctly proxying requests to your PHP processor (Gunicorn/FPM).
Example WordPress Site Configuration
# /etc/nginx/sites-available/your-wordpress-site
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
# Redirect HTTP to HTTPS (if SSL is configured)
# return 301 https://$host$request_uri;
# Root directory and index files
root /var/www/your-wordpress-site/public_html;
index index.php index.html index.htm;
# Caching for static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 365d;
add_header Cache-Control "public, no-transform";
access_log off;
log_not_found off;
}
# Prevent access to sensitive files
location ~ /\.ht {
deny all;
}
# PHP processing (using FastCGI for FPM)
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other FastCGI processors)
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version and socket path
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to wp-config.php
location ~ wp-config\.php$ {
deny all;
}
# WordPress permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# Optional: Security headers
# add_header X-Frame-Options "SAMEORIGIN";
# add_header X-Content-Type-Options "nosniff";
# add_header Referrer-Policy "strict-origin-when-cross-origin";
# add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";
}
Applying Nginx Changes
After modifying Nginx configuration files, always test the configuration for syntax errors before reloading the service. This prevents downtime.
Testing and Reloading Nginx
sudo nginx -t # If syntax is OK, reload Nginx sudo systemctl reload nginx
Gunicorn/PHP-FPM: The Application Backend
For WordPress, PHP-FPM (FastCGI Process Manager) is the standard for handling PHP requests. If you’re using a framework like Laravel or Django with WordPress, you might integrate Gunicorn. This section focuses on PHP-FPM tuning.
PHP-FPM Configuration Tuning
PHP-FPM’s performance is heavily influenced by its process management. The key directives are found in /etc/php/8.1/fpm/pool.d/www.conf (adjust PHP version and pool name as needed).
Process Manager Settings
The pm directive can be set to static, dynamic, or ondemand. For WordPress, dynamic or static are generally preferred for consistent performance.
Tuning Directives for dynamic PM
; /etc/php/8.1/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /var/run/php/php8.1-fpm.sock # Match Nginx config listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 50 ; Max number of children at any one time pm.start_servers = 5 ; Number of children created at startup pm.min_spare_servers = 5 ; Min number of idle/spare children pm.max_spare_servers = 10 ; Max number of idle/spare children pm.process_idle_timeout = 10s ; How long to keep idle processes pm.max_requests = 500 ; Max requests per child process before respawning
Explanation:
pm.max_children: This is the most critical setting. It should be high enough to handle peak concurrent requests but not so high that it exhausts server memory. A common formula is(Total RAM - RAM for OS/DB/Nginx) / Average PHP Process Size. Monitor memory usage and adjust.pm.start_servers: The number of processes spawned when PHP-FPM starts.pm.min_spare_serversandpm.max_spare_servers: PHP-FPM will try to maintain this number of idle processes.pm.max_requests: Setting this to a reasonable number helps prevent memory leaks in long-running processes.
Tuning Directives for static PM
; /etc/php/8.1/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /var/run/php/php8.1-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = static pm.max_children = 50 ; Fixed number of children pm.max_requests = 500 ; Max requests per child process before respawning
Explanation:
pm.max_children: With static, this is the *fixed* number of processes that will always be running. This offers more predictable performance but can be less efficient if traffic is highly variable.
Applying PHP-FPM Changes
Similar to Nginx, test and reload PHP-FPM after making configuration changes.
Testing and Reloading PHP-FPM
sudo php-fpm8.1 -t # Adjust PHP version # If syntax is OK, reload PHP-FPM sudo systemctl reload php8.1-fpm # Adjust PHP version
MySQL/MariaDB Optimization for WordPress
The database is often the bottleneck for dynamic WordPress sites. Optimizing MySQL/MariaDB involves tuning its buffer pools, connection handling, and query cache (though query cache is deprecated in newer MySQL versions and often less effective than other caching mechanisms).
MySQL/MariaDB Configuration Tuning
The primary configuration file is typically /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf. We’ll focus on key InnoDB settings and connection parameters.
InnoDB Buffer Pool
The innodb_buffer_pool_size is arguably the most important setting. It caches data and indexes for InnoDB tables. A common recommendation is 50-75% of available RAM on a dedicated database server, or a smaller percentage (e.g., 25-50%) on a shared server.
Connection Handling
Query Cache (MySQL < 8.0)
For older MySQL versions, the query cache can sometimes help, but it’s known to have scalability issues with frequent writes. If you’re on MySQL 8.0+, this is disabled by default and removed in later versions. Consider application-level or object caching (e.g., Redis, Memcached) instead.
Example MySQL/MariaDB Configuration Snippet
# /etc/mysql/mariadb.conf.d/50-server.cnf (or equivalent) [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_dir = /usr/share/mysql lc_messages = en_US skip-external-locking # InnoDB Settings innodb_buffer_pool_size = 1G ; Adjust based on server RAM (e.g., 1G for 4GB RAM server) innodb_log_file_size = 256M ; Larger log files can improve write performance innodb_flush_log_at_trx_commit = 1 ; For ACID compliance (0 or 2 for higher performance at risk) innodb_flush_method = O_DIRECT ; Recommended for Linux innodb_file_per_table = 1 ; Recommended for better management # Connection Settings max_connections = 150 ; Adjust based on expected concurrent connections thread_cache_size = 16 ; Cache threads for reuse table_open_cache = 2000 ; Cache open table file descriptors table_definition_cache = 1000 ; Cache table definitions # Query Cache (MySQL < 8.0, generally not recommended for high-write loads) # query_cache_type = 1 # query_cache_size = 64M # query_cache_limit = 1M # Other optimizations sort_buffer_size = 2M join_buffer_size = 2M read_rnd_buffer_size = 1M read_buffer_size = 1M tmp_table_size = 64M max_heap_table_size = 64M key_buffer_size = 16M ; For MyISAM, if used (less common for WP)
Note: The exact values for innodb_buffer_pool_size, max_connections, and other memory-related settings depend heavily on your server’s RAM and the specific workload. Always monitor your server’s performance and resource utilization after making changes.
Applying MySQL/MariaDB Changes
After modifying the MySQL/MariaDB configuration, restart the service.
Restarting MySQL/MariaDB
sudo systemctl restart mysql # Or mariadb
Monitoring and Iterative Tuning
Tuning is not a one-time event. Continuous monitoring is crucial to identify bottlenecks and validate the effectiveness of your changes. Use tools like:
Key Monitoring Tools and Metrics
- Nginx:
ngx_http_stub_status_module(for active connections, requests per second), Nginx access/error logs,htop/topfor CPU/memory. - PHP-FPM: PHP-FPM status page (if enabled),
htop/top, PHP error logs. - MySQL/MariaDB:
SHOW GLOBAL STATUS;,SHOW ENGINE INNODB STATUS;,mysqltuner.plscript,pt-query-digestfor slow query analysis,htop/top. - System-wide:
htop,iotop,vmstat, Prometheus/Grafana for long-term trend analysis.
Start with conservative settings and gradually increase them while observing resource utilization. If you encounter issues like high CPU usage, excessive swapping, or slow response times, revisit your configurations. For WordPress, consider implementing object caching (Redis/Memcached) and a robust CDN to further offload your server.