The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on Linode for PHP
Nginx as a High-Performance Frontend Proxy
For a PHP application, Nginx serves as an excellent frontend proxy, efficiently handling static assets, SSL termination, and request routing to your application server (Gunicorn for Python/Flask/Django, or PHP-FPM for PHP). Optimizing Nginx is crucial for overall system responsiveness.
Nginx Configuration Tuning
The primary configuration file is typically located at /etc/nginx/nginx.conf. We’ll focus on key directives within the http block and within your specific server block.
Global Nginx Settings
These settings affect the Nginx worker processes. Adjusting worker_processes to match your CPU cores is a common starting point. worker_connections dictates the maximum number of simultaneous connections a worker can handle.
user www-data;
worker_processes auto; # Set to the number of CPU cores, or 'auto'
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024; # Adjust based on expected load and server RAM
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off; # Important for security
# 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;
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL settings (if applicable)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
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';
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Include virtual host configurations
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Server Block Optimization
Within your site’s server block (e.g., /etc/nginx/sites-available/your_app), focus on caching, request handling, and proxying.
server {
listen 80;
listen [::]:80;
server_name your_domain.com www.your_domain.com;
# Redirect HTTP to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain.com www.your_domain.com;
# SSL Certificate paths
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Static file caching
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Proxy to Gunicorn/PHP-FPM
location / {
proxy_pass http://unix:/run/gunicorn.sock; # Or http://127.0.0.1:8000; for TCP
# For PHP-FPM:
# include snippets/fastcgi-php.conf;
# fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Error pages
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Gunicorn Tuning for Python Applications
Gunicorn (Green Unicorn) is a Python WSGI HTTP Server. Its performance is heavily influenced by the number of worker processes and threads.
Worker Processes and Threads
The general recommendation is to set the number of worker processes to (2 * Number of CPU Cores) + 1. For threaded workers (using the gthread worker type), you can further tune the number of threads per worker.
# Example command to start Gunicorn
gunicorn --workers 3 \
--worker-class gthread \
--threads 2 \
--bind unix:/run/gunicorn.sock \
--timeout 120 \
your_app.wsgi:application
# Explanation:
# --workers 3: (2 * 1 CPU core) + 1 = 3 workers
# --worker-class gthread: Uses threads for concurrency within each worker process.
# --threads 2: Each worker process will have 2 threads.
# --bind unix:/run/gunicorn.sock: Binds to a Unix socket for faster inter-process communication with Nginx.
# --timeout 120: Sets the worker timeout to 120 seconds. Adjust based on your application's longest-running requests.
For CPU-bound tasks, the default sync worker class might be sufficient, but for I/O-bound applications, gevent or gthread with multiple threads can offer better concurrency. If using gevent, ensure you have it installed (`pip install gevent`).
# Example with gevent
gunicorn --workers 3 \
--worker-class gevent \
--bind unix:/run/gunicorn.sock \
--timeout 120 \
your_app.wsgi:application
Systemd Service File for Gunicorn
Managing Gunicorn with systemd is standard practice for production environments.
[Unit]
Description=Gunicorn instance to serve your_app
After=network.target
[Service]
User=your_user
Group=www-data
WorkingDirectory=/path/to/your_app
ExecStart=/path/to/your_virtualenv/bin/gunicorn \
--workers 3 \
--worker-class gthread \
--threads 2 \
--bind unix:/run/gunicorn.sock \
--timeout 120 \
your_app.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Save this as /etc/systemd/system/gunicorn.service and enable/start it with:
sudo systemctl enable gunicorn sudo systemctl start gunicorn sudo systemctl status gunicorn
PHP-FPM Tuning for PHP Applications
PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with web servers like Nginx. Its performance hinges on the process manager settings.
Process Manager Configuration
The configuration file is typically found at /etc/php/X.Y/fpm/pool.d/www.conf (replace X.Y with your PHP version, e.g., 8.1).
[www] user = www-data group = www-data listen = /var/run/php/php8.1-fpm.sock ; Or a TCP socket like 127.0.0.1:9000 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 = 2 ; Number of children to keep active at minimum pm.max_spare_servers = 10 ; Number of children to keep active at maximum pm.process_idle_timeout = 10s ; Timeout for idle processes to be killed ; For CPU-bound applications, consider 'ondemand' or 'static' ; pm = ondemand ; pm.max_children = 50 ; pm.process_idle_timeout = 10s ; pm = static ; pm.max_children = 50 request_terminate_timeout = 120 ; Max execution time for a script request_slowlog_timeout = 30s ; Log scripts that take longer than this slowlog = /var/log/php/php8.1-fpm.slow.log ; Other useful settings ; php_admin_value[memory_limit] = 256M ; php_admin_value[upload_max_filesize] = 64M ; php_admin_value[post_max_size] = 64M
Understanding PM settings:
dynamic: The most common setting. PHP-FPM starts withpm.start_servers, and dynamically spawns/kills processes based on load, staying withinpm.min_spare_serversandpm.max_spare_servers, up topm.max_children.static: PHP-FPM keeps a fixed number of processes running (pm.max_children). This can be faster if you have consistent high traffic but wastes resources during low traffic.ondemand: Processes are only spawned when a request is received and killed after a period of inactivity (pm.process_idle_timeout). This saves resources but can introduce slight latency on the first request after idle periods.
The optimal values for pm.max_children, pm.start_servers, etc., depend heavily on your server’s RAM and the memory footprint of your PHP application. A good starting point for pm.max_children is (Total RAM - RAM for OS/Nginx/DB) / Average PHP Process Memory. Monitor your server’s memory usage and adjust accordingly.
MongoDB Performance Tuning
MongoDB performance is critical for applications that rely on it as a primary data store. Tuning involves configuration, indexing, and query optimization.
MongoDB Configuration (`mongod.conf`)
The primary configuration file is usually /etc/mongod.conf.
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
engine: wiredTiger # Default and recommended storage engine
# network:
# bindIp: 127.0.0.1 # Bind to localhost only for security if not accessed remotely
# processManagement:
# fork: true
# pidFilePath: /var/run/mongodb/mongod.pid
# logFilePath: /var/log/mongodb/mongod.log
# systemLog:
# destination: file
# path: /var/log/mongodb/mongod.log
# logAppend: true
# verbosity: 0
# security:
# authorization: enabled # Highly recommended for production
# sharding:
# clusterRole: configsvr
# configsvrFilePattern: /var/lib/mongodb/cs.metadata.json
# configsvrDataDir: /var/lib/mongodb/cs
# replication:
# oplogSizeMB: 10240 # Adjust oplog size for replica sets
# WiredTiger specific settings (can be tuned further)
# wiredTiger:
# engineConfig:
# cacheSizeGB: 0.5 # Example: Allocate 50% of available RAM to WiredTiger cache, adjust as needed
# collectionConfig:
# blockCompressor: snappy # Or zlib, zstd for better compression at CPU cost
# indexConfig:
# prefixCompression: true
Key Tuning Points:
storage.engine:wiredTigeris the default and generally the best choice.storage.wiredTiger.engineConfig.cacheSizeGB: This is crucial. Allocate a significant portion of your server’s RAM to the WiredTiger cache. A common recommendation is 50% of available RAM, but monitor usage.storage.wiredTiger.collectionConfig.blockCompressor:snappyoffers a good balance of compression and CPU usage.zliborzstdoffer higher compression but use more CPU.replication.oplogSizeMB: If running a replica set, ensure the oplog is large enough to accommodate write operations between elections or network interruptions.security.authorization: Always enable authentication in production.
Indexing Strategy
Proper indexing is paramount for MongoDB query performance. Use explain() to analyze query performance and identify missing indexes.
// Example: Analyzing a find query
db.collection.find({ status: "active", type: "user" }).explain("executionStats")
// If the output shows 'COLLSCAN' (Collection Scan) or high 'totalKeysExamined' vs 'totalDocsExamined',
// an index is likely needed.
// Creating a compound index
db.collection.createIndex({ status: 1, type: 1 })
// Creating a text index for full-text search
db.collection.createIndex({ title: "text", body: "text" })
// Dropping an index
db.collection.dropIndex("index_name")
Consider the order of fields in compound indexes. MongoDB uses indexes from left to right. Place fields with higher cardinality (more unique values) earlier in the index.
Query Optimization
Avoid fetching more data than necessary. Use projection to limit returned fields.
// Fetching only specific fields
db.collection.find({ status: "active" }, { name: 1, email: 1, _id: 0 })
// This query returns only 'name' and 'email', excluding '_id'
Be mindful of aggregation pipeline stages. Some stages are more resource-intensive than others. Ensure stages like $match are placed as early as possible to filter documents before expensive operations.
Putting It All Together: Linode Deployment Workflow
When deploying or updating your application on Linode, follow a structured workflow:
- Provision Linode Instance: Choose an instance size appropriate for your expected load.
- Install Dependencies: Install Nginx, PHP (if applicable), Gunicorn (if applicable), Python, MongoDB, and any other required packages.
- Configure MongoDB: Edit
/etc/mongod.conf, restart MongoDB, and ensure it’s running. - Configure PHP-FPM (if applicable): Edit
www.conf, restart PHP-FPM. - Configure Gunicorn (if applicable): Create/edit the systemd service file, start Gunicorn.
- Configure Nginx: Edit
nginx.confand your site’s server block. - Test Nginx Configuration: Run
sudo nginx -t. - Reload/Restart Nginx:
sudo systemctl reload nginxorsudo systemctl restart nginx. - Monitor Performance: Use tools like
htop,iotop, Nginx status module, PHP-FPM status page, MongoDB’smongostatandmongotop, and application-level monitoring to identify bottlenecks. - Iterate and Tune: Based on monitoring, adjust configurations (e.g., worker counts, cache sizes, timeouts) and re-test.
This comprehensive approach to tuning Nginx, your application server (Gunicorn/PHP-FPM), and your database (MongoDB) on Linode will provide a robust and performant foundation for your web applications.