The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on Google Cloud for Laravel
Nginx as a High-Performance Frontend for Laravel
When deploying Laravel applications, Nginx serves as an excellent choice for a web server and reverse proxy. Its event-driven, asynchronous architecture makes it highly efficient at handling concurrent connections. For optimal performance, we’ll focus on tuning worker processes, connection limits, and caching mechanisms.
Nginx Configuration Tuning
The primary configuration file for Nginx is typically located at /etc/nginx/nginx.conf. We’ll adjust the http block for global settings and the server block for our specific Laravel application.
Global Nginx Settings
The worker_processes directive should ideally be set to the number of CPU cores available on your instance. This allows Nginx to utilize all available processing power. worker_connections defines the maximum number of simultaneous connections that each worker process can open. A common starting point is 1024, but this can be increased based on your server’s memory and expected load.
Tuning nginx.conf
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto; # Set to the number of CPU cores, or 'auto' for dynamic adjustment
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;
# SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# Logging settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# 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;
# Caching
open_file_cache max=2000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Laravel Application Server Block
This configuration assumes you are using PHP-FPM for PHP execution. If you are using Gunicorn with a Python framework (like Django or Flask), the principles are similar, but the fastcgi_pass directive would be replaced with a proxy_pass to your Gunicorn socket or IP:port.
Tuning laravel_app.conf
# /etc/nginx/sites-available/laravel_app.conf
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 /var/www/your_laravel_app/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# Adjust this to your PHP-FPM socket or IP:port
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
location ~ /\.ht {
deny all;
}
# Caching for static assets
location ~* \.(css|js|jpg|jpeg|gif|png|svg|ico|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Disable access to hidden files
location ~ /\. {
deny all;
}
}
PHP-FPM (or Gunicorn) Tuning for Laravel
PHP-FPM (FastCGI Process Manager) is crucial for efficiently handling PHP requests. Tuning its process manager settings can significantly impact performance. The configuration file is typically /etc/php/8.1/fpm/pool.d/www.conf (version may vary).
PHP-FPM Process Manager Settings
The pm directive can be set to dynamic, static, or ondemand. For most web applications, dynamic offers a good balance between resource utilization and responsiveness. pm.max_children is the most critical setting, defining the maximum number of child processes that can be spawned. This should be calculated based on your server’s RAM and the memory footprint of your PHP application.
Tuning www.conf
; /etc/php/8.1/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /var/run/php/php8.1-fpm.sock ; Matches Nginx config ; Process Manager settings pm = dynamic pm.max_children = 100 ; Adjust based on server RAM and app memory usage pm.start_servers = 5 ; Number of processes started when FPM starts pm.min_spare_servers = 2 ; Minimum number of idle processes pm.max_spare_servers = 10 ; Maximum number of idle processes pm.max_requests = 500 ; Number of requests each child process will serve before respawning ; Other useful settings request_terminate_timeout = 60s ; Timeout for script execution ; rlimit_files = 1024 ; rlimit_nofile = 65536
Gunicorn (Python Alternative): If using Gunicorn, you’d typically configure workers via the --workers flag. A common recommendation is (2 * CPU_CORES) + 1. For example, on a 4-core machine, you might use 9 workers. You’d also configure the worker class (e.g., gevent for async I/O).
Example Gunicorn Command Line
gunicorn --workers 9 --worker-class gevent --bind unix:/path/to/your/app.sock wsgi:app
PostgreSQL Performance Tuning on Google Cloud
PostgreSQL’s performance is heavily influenced by its configuration parameters. On Google Cloud, you’ll typically be using Cloud SQL for PostgreSQL. While Google manages the underlying infrastructure, tuning the database parameters is still your responsibility. The primary configuration file is postgresql.conf.
Key PostgreSQL Configuration Parameters
Several parameters are critical for performance. These are often set in postgresql.conf or can be modified via ALTER SYSTEM SET parameter = value; in psql.
Tuning postgresql.conf (or via ALTER SYSTEM)
# postgresql.conf or ALTER SYSTEM SET parameter = value; # Memory Management shared_buffers = 25% of total RAM ; e.g., 8GB for a 32GB instance work_mem = 16MB ; Adjust based on complex queries and available RAM maintenance_work_mem = 128MB ; For VACUUM, CREATE INDEX, etc. # Checkpointing checkpoint_completion_target = 0.9 ; Spread checkpoint writes over time max_wal_size = 2GB ; Amount of WAL files before checkpointing min_wal_size = 512MB ; Minimum WAL size # Background Writer bgwriter_delay = 10ms ; Delay between bgwriter runs bgwriter_lru_maxpages = 1000 ; Max pages bgwriter can write per round bgwriter_lru_multiplier = 2.0 ; Multiplier for LRU scan # Connection Management max_connections = 100 ; Adjust based on application needs and server resources # Query Planning random_page_cost = 1.1 ; Lower for SSDs, default is 4.0 seq_page_cost = 1.0 ; Default is 1.0 # WAL (Write-Ahead Logging) wal_buffers = 16MB ; For WAL writes wal_writer_delay = 200ms ; Delay for WAL writer # Autovacuum autovacuum = on autovacuum_max_workers = 3 ; Number of autovacuum worker processes autovacuum_naptime = 15s ; Time to sleep between vacuum runs autovacuum_vacuum_threshold = 50 ; Min number of row updates before vacuum autovacuum_analyze_threshold = 50 ; Min number of row inserts/updates/deletes before analyze
Important Note on Cloud SQL: When using Cloud SQL, you manage these settings through the Google Cloud Console under the instance’s “Flags” section. Changes made here are persistent and applied after a restart of the instance.
Monitoring and Iteration
Performance tuning is an iterative process. Use monitoring tools to observe the impact of your changes. Key metrics to watch include:
- Nginx: Active connections, requests per second, error rates (4xx, 5xx), worker connections usage.
- PHP-FPM: Process manager status (idle, active, waiting), request duration, memory usage per process.
- PostgreSQL: CPU utilization, memory usage (especially
shared_buffershit ratio), I/O wait times, slow queries (usingpg_stat_statements), connection counts, WAL write activity.
Tools like Google Cloud’s Cloud Monitoring, Prometheus with Grafana, and PostgreSQL’s built-in statistics views (pg_stat_activity, pg_stat_statements) are invaluable. Regularly analyze slow queries in PostgreSQL and optimize your Laravel application’s database interactions, including indexing and query structure.