The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on Google Cloud for Laravel
Nginx as a High-Performance Frontend for Laravel
When deploying Laravel applications on Google Cloud, Nginx serves as an indispensable frontend. Its efficiency in handling static assets, SSL termination, and request routing makes it a cornerstone of a performant infrastructure. We’ll focus on tuning Nginx for optimal throughput and low latency, particularly in conjunction with PHP-FPM for traditional PHP deployments or Gunicorn for PHP-FPM via a WSGI bridge.
Nginx Configuration for Laravel
A robust Nginx configuration is critical. Key directives to consider include worker processes, connection limits, caching strategies, and efficient proxying to your application server.
Tuning `nginx.conf`
The main Nginx configuration file, typically located at /etc/nginx/nginx.conf, requires careful tuning. The worker_processes directive should ideally be set to the number of CPU cores available on your instance. For a typical Google Cloud Compute Engine instance, this can be determined dynamically or set based on instance type.
worker_processes auto; # Or set to the number of CPU cores
# For example, if you have 4 cores: worker_processes 4;
events {
worker_connections 1024; # Adjust based on expected concurrent connections
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# 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;
# Buffering and timeouts for upstream connections
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
# Enable HTTP/2 for improved performance
http2 on;
# Include virtual host configurations
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
The worker_connections directive defines the maximum number of simultaneous connections that a single worker process can open. This should be set considering your expected traffic load and the limits imposed by your operating system (e.g., ulimit -n).
Laravel Site Configuration (`laravel.conf`)
Each Laravel application typically resides in its own server block. This block handles SSL termination, static file serving, and proxying requests to the application server (Gunicorn/PHP-FPM).
server {
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;
server_name your-domain.com www.your-domain.com;
# SSL Configuration
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;
# HSTS Header
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/your-laravel-app/public;
index index.php index.html index.htm;
# Serve static assets directly
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# PHP-FPM configuration (if using PHP-FPM directly)
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version and socket path
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
}
# Proxy to Gunicorn (if using Gunicorn with PHP-FPM via WSGI)
# location / {
# proxy_pass http://unix:/path/to/your/app.sock; # Or http://localhost:8000
# 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;
# }
# Laravel specific rules
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Deny access to .env files
location ~ /\.env {
deny all;
}
}
The try_files directive is crucial for Laravel. It first checks for the existence of the requested URI as a file, then as a directory, and finally falls back to passing the request to index.php. This allows Nginx to serve static assets directly while routing dynamic requests to PHP-FPM.
Gunicorn/PHP-FPM Tuning for Laravel
The application server is responsible for executing your PHP code. Whether you’re using PHP-FPM directly or Gunicorn (often with a WSGI adapter like php-fpm-wsgi for PHP applications), tuning its worker processes and memory limits is vital.
PHP-FPM Configuration (`php-fpm.conf` and `pool.d/www.conf`)
PHP-FPM’s performance is governed by its pool configuration, typically found in /etc/php/X.Y/fpm/pool.d/www.conf (replace X.Y with your PHP version). The pm (process manager) setting is key.
pm = dynamic: PHP-FPM will manage the number of child processes dynamically.pm = static: A fixed number of child processes will be maintained. This can be more predictable but less flexible.
For dynamic process management:
; /etc/php/X.Y/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /var/run/php/phpX.Y-fpm.sock # Ensure this matches Nginx config
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50 # Adjust based on available RAM and expected load
pm.min_spare_servers = 5 # Minimum number of idle servers
pm.max_spare_servers = 10 # Maximum number of idle servers
pm.process_idle_timeout = 10s; # How long to keep idle processes alive
pm.max_requests = 500 # Restart a child process after this many requests
# Helps prevent memory leaks
For static process management (often preferred for predictable workloads):
; /etc/php/X.Y/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /var/run/php/phpX.Y-fpm.sock
listen.owner = www-data
group = www-data
listen.mode = 0660
pm = static
pm.max_children = 30 # Fixed number of processes. Calculate based on RAM.
# (Total RAM - OS/Nginx - MongoDB) / RAM per PHP process
pm.max_requests = 0 # Disable automatic process restart
The pm.max_children (or pm.max_children for static) is the most critical setting. Over-allocating can lead to OOM (Out Of Memory) errors, while under-allocating will bottleneck your application. A common rule of thumb for static is to calculate the total available RAM, subtract memory for the OS, Nginx, and MongoDB, then divide the remainder by the estimated memory footprint of a single PHP-FPM worker process (which can be found using tools like ps aux --sort=-%mem or by monitoring with tools like htop).
Gunicorn Configuration (if applicable)
If you’re using Gunicorn to serve a PHP application via a WSGI adapter (less common but possible), its configuration is similar to Python applications. The number of workers is paramount.
# Example Gunicorn command line gunicorn --workers 4 --threads 2 --bind unix:/path/to/your/app.sock wsgi:application # Or for a TCP socket # gunicorn --workers 4 --threads 2 --bind 127.0.0.1:8000 wsgi:application
A common recommendation for Gunicorn workers is (2 * number_of_cores) + 1. Threads can help with I/O-bound tasks but add complexity. For PHP applications, relying on PHP-FPM’s process management is generally more straightforward.
MongoDB Tuning on Google Cloud
MongoDB’s performance is heavily influenced by its configuration, hardware, and indexing strategy. On Google Cloud, this translates to choosing the right machine types and configuring MongoDB appropriately.
`mongod.conf` Configuration
The primary configuration file for MongoDB is typically /etc/mongod.conf. Key parameters for performance include storage engine settings, journaling, and cache size.
# /etc/mongod.conf
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
engine: wiredTiger # Recommended storage engine
# For WiredTiger, the cache size is managed automatically.
# However, you can set a target size if needed, but it's often best to let it auto-tune.
# If you need to explicitly set it (e.g., for older versions or specific tuning):
# wiredTiger:
# engineConfig:
# cacheSizeGB: 4 # Example: 4GB cache. Adjust based on available RAM.
# # A common recommendation is 50% of system RAM for dedicated DB servers.
# Network interfaces to bind to.
# For Google Cloud, binding to 0.0.0.0 is common if firewalls are properly configured.
# Or bind to a specific internal IP for enhanced security.
net:
port: 27017
bindIp: 0.0.0.0 # Or your instance's internal IP address
# Logging configuration
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# Security settings (essential for production)
security:
authorization: enabled # Always enable authorization
# Sharding settings (if applicable)
# sharding:
# clusterRole: configsvr
# configsvrFilePermissions:
# cluster:
# group: mongodb
# mode: "0600"
# local:
# group: mongodb
# mode: "0600"
# Process management
processManagement:
fork: true
pidFilePath: /var/run/mongodb/mongod.pid
# Ensure the directory exists and has correct permissions
# mkdir -p /var/run/mongodb && chown mongodb:mongodb /var/run/mongodb
The wiredTiger storage engine is the default and generally recommended. Its cache is crucial. For dedicated MongoDB instances on Google Cloud, allocating 50-75% of the instance’s RAM to the WiredTiger cache is a common starting point. For example, on a 32GB RAM instance, you might set cacheSizeGB: 16 or 24. However, MongoDB’s automatic cache management is often sufficient. Monitor cache usage with db.serverStatus().wiredTiger.cache.
Indexing Strategy
Effective indexing is paramount for MongoDB performance. Without proper indexes, MongoDB will resort to collection scans, which are extremely inefficient. Analyze your application’s query patterns using MongoDB’s explain() method and tools like mongotop and mongostat.
// Example: Analyzing a query
db.collection.find({ user_id: 123, status: "active" }).explain()
// Example: Creating a compound index
db.collection.createIndex({ user_id: 1, status: 1 })
// Example: Creating a text index for search
db.collection.createIndex({ title: "text", body: "text" })
Regularly review slow queries and ensure appropriate indexes exist. Google Cloud’s operations suite (formerly Stackdriver) can help monitor database performance and identify slow queries.
Google Cloud Specific Considerations
When deploying on Google Cloud, consider the following:
- Machine Types: Choose machine types with sufficient CPU and RAM for your workload. For I/O-intensive MongoDB workloads, consider instances with local SSDs for faster disk I/O, though this comes with data persistence caveats.
- Network Performance: Ensure your Compute Engine instances are in the same region and zone as your MongoDB deployment (if self-hosted) for low latency. If using Cloud SQL for other services, ensure network configurations are optimized.
- Managed Services: For production environments, consider using Google Cloud’s fully managed MongoDB Atlas (via Atlas on GCP) or Cloud Bigtable for massive scale, as they offload much of the operational burden and offer robust performance tuning.
- Firewall Rules: Configure VPC firewall rules to restrict access to your MongoDB port (default 27017) only from your application servers.
Monitoring and Iteration
Performance tuning is an ongoing process. Implement comprehensive monitoring for Nginx, PHP-FPM/Gunicorn, and MongoDB. Key metrics include:
- Nginx: Request rate, error rate (4xx, 5xx), connection count, latency.
- PHP-FPM/Gunicorn: Process count, request queue length, memory usage, CPU usage, request duration.
- MongoDB: Query latency, read/write operations, cache hit rate, disk I/O, network traffic, connection count, oplog lag (for replica sets).
Tools like Prometheus with Grafana, Datadog, or Google Cloud’s operations suite are invaluable for collecting, visualizing, and alerting on these metrics. Regularly review these metrics, identify bottlenecks, and iterate on your configurations.