The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on DigitalOcean for Shopify
Nginx as a High-Performance Frontend Proxy
For a Shopify store, Nginx serves as the critical entry point, handling SSL termination, static asset delivery, and proxying dynamic requests to your application backend (Gunicorn for Python or PHP-FPM for PHP). Optimizing Nginx is paramount for low latency and high throughput.
Nginx Configuration Tuning
We’ll focus on key directives within your nginx.conf or a site-specific configuration file (e.g., /etc/nginx/sites-available/your_shopify_site).
Worker Processes and Connections
The worker_processes directive should ideally be set to the number of CPU cores available on your DigitalOcean droplet. worker_connections defines the maximum number of simultaneous connections a worker process can handle. A common starting point is 1024, but this can be increased based on load.
user www-data;
worker_processes auto; # Or set to the number of CPU cores, e.g., worker_processes 4;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 4096; # Increased from 1024
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
# ... other http configurations
}
Buffering and Caching
Nginx buffering can significantly impact performance. Tuning client_body_buffer_size and proxy_buffers is crucial. For static assets, leverage Nginx’s built-in caching capabilities.
http {
# ... other http configurations
client_body_buffer_size 128k;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Static asset caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# ... proxy_pass configuration to your backend
}
Gzip Compression
Enabling Gzip compression for text-based assets (HTML, CSS, JS) dramatically reduces transfer sizes.
http {
# ... other http configurations
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;
gzip_min_length 1000; # Only compress responses larger than 1KB
}
Gunicorn (Python/Django/Flask) Optimization
When using Gunicorn as your Python WSGI HTTP Server, tuning the number of worker processes and threads is key. For CPU-bound workloads, more worker processes are generally better. For I/O-bound workloads, increasing threads per worker can be beneficial.
Worker Processes and Threads
A common recommendation is to set the number of workers to (2 * number_of_cores) + 1. Threads can be adjusted based on your application’s I/O patterns.
# Example Gunicorn command line gunicorn --workers 5 --threads 2 --bind 0.0.0.0:8000 your_project.wsgi:application
In a systemd service file (e.g., /etc/systemd/system/gunicorn.service), this might look like:
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=your_user
Group=www-data
WorkingDirectory=/path/to/your/project
ExecStart=/path/to/your/venv/bin/gunicorn \
--workers 5 \
--threads 2 \
--bind unix:/run/gunicorn.sock \
your_project.wsgi:application
[Install]
WantedBy=multi-user.target
Gunicorn Timeout and Keepalive
Adjusting the --timeout value prevents workers from being killed prematurely on long-running requests, while --keep-alive can improve performance by reusing connections.
gunicorn --workers 5 --threads 2 --timeout 120 --keep-alive 5 --bind unix:/run/gunicorn.sock your_project.wsgi:application
PHP-FPM Optimization
For PHP applications, PHP-FPM (FastCGI Process Manager) is the standard. Tuning its process management and memory limits is crucial.
Process Management (pm)
PHP-FPM offers several process management strategies: static, dynamic, and ondemand. dynamic is often a good balance, allowing FPM to scale workers based on load.
[www] user = www-data group = www-data listen = /run/php/php7.4-fpm.sock # Adjust version as needed 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 when FPM starts pm.min_spare_servers = 2 # Min number of idle servers pm.max_spare_servers = 10 # Max number of idle servers pm.process_idle_timeout = 10s; # Timeout for idle processes to be killed pm.max_requests = 500 # Max requests a child process should execute
The values for pm.max_children, pm.start_servers, etc., should be tuned based on your droplet’s RAM and expected traffic. A common starting point for pm.max_children is (Total RAM - RAM for OS/other services) / Average Process Size.
PHP Memory Limits
Ensure your memory_limit in php.ini is sufficient for your Shopify theme and plugins, but not excessively high to prevent memory exhaustion.
; Find your php.ini file (e.g., /etc/php/7.4/fpm/php.ini) memory_limit = 256M upload_max_filesize = 64M post_max_size = 64M
Redis for Caching and Session Management
Redis is an in-memory data structure store, perfect for caching frequently accessed data and managing user sessions, significantly offloading your database and application server.
Redis Configuration Tuning
Key directives in redis.conf include maxmemory and maxmemory-policy.
# Find your redis.conf file (e.g., /etc/redis/redis.conf) daemonize yes pidfile /var/run/redis_6379.pid logfile /var/log/redis/redis-server.log bind 127.0.0.1 -::1 # Bind to localhost if Nginx/App are on the same server # Memory management maxmemory 512mb # Set to a reasonable portion of your droplet's RAM maxmemory-policy allkeys-lru # Evict least recently used keys when maxmemory is reached # Persistence (optional, depending on use case) # save 900 1 # save 300 10 # save 60 10000 # appendonly no
For a Shopify store, allkeys-lru is a sensible maxmemory-policy, ensuring that the least recently accessed data is discarded when memory is full. If Redis is only used for caching and sessions, disabling persistence (save and appendonly no) can improve performance, as data loss on restart is acceptable.
Client-Side Configuration (Shopify/Application)
Ensure your Shopify application (or its backend framework) is configured to use Redis for caching and sessions. This typically involves setting up a Redis client library and pointing it to your Redis instance.
# Example for Django
# settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1', # Database 1 for cache
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
SESSION_ENGINE = 'django_redis.session.SessionStore'
SESSION_REDIS = {
'HOST': 'localhost',
'PORT': 6379,
'DB': 0, # Database 0 for sessions
'PASSWORD': None,
'PREFIX': 'session'
}
Monitoring and Iteration
Continuous monitoring is essential. Use tools like htop, nginx-top, redis-cli monitor, and application-specific performance monitoring (APM) tools to identify bottlenecks. Regularly review logs for errors and performance warnings. Tuning is an iterative process; make small, measured changes and observe their impact.