• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on Linode for Laravel

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on Linode for Laravel

Nginx as a High-Performance Frontend Proxy

For a Laravel application, Nginx serves as the ideal frontend proxy. Its event-driven, asynchronous architecture makes it exceptionally efficient at handling concurrent connections, serving static assets, and buffering requests before they reach your application server (Gunicorn for Python/Flask/Django, or PHP-FPM for PHP/Laravel). The key is to configure Nginx to offload as much work as possible and to pass requests to the backend efficiently.

Nginx Configuration for Laravel

We’ll focus on a production-ready Nginx configuration. This involves optimizing worker processes, caching, compression, and secure SSL termination. Assume your Laravel application is running on a Linode instance, and you’re using a domain name pointing to its IP address.

Core Nginx Settings

Edit your main Nginx configuration file, typically located at /etc/nginx/nginx.conf. These global settings affect all worker processes.

Worker Processes: Set worker_processes to the number of CPU cores available on your server. This allows Nginx to utilize all available processing power.

Worker Connections: worker_connections defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024 or higher, depending on your expected traffic. The total maximum connections will be worker_processes * worker_connections.

Event Handling: Use the epoll event method on Linux for optimal performance.

File Descriptors: Increase the worker_rlimit_nofile to a sufficiently high number to avoid “too many open files” errors under heavy load.

Example nginx.conf Snippet

Add or modify these directives in your /etc/nginx/nginx.conf:

# /etc/nginx/nginx.conf

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 4096; # Adjust based on expected load and server resources
    multi_accept on;
    use epoll;
}

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

    # Gzip Compression
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 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/*;
}

Laravel Site Configuration

Create a new server block configuration file for your Laravel application, typically in /etc/nginx/sites-available/your-app.conf and then symlink it to /etc/nginx/sites-enabled/.

This configuration will handle:

  • SSL termination (using Let’s Encrypt/Certbot).
  • Serving static assets directly.
  • Proxying dynamic requests to PHP-FPM.
  • Setting appropriate headers for security and performance.

Example your-app.conf (with PHP-FPM)

# /etc/nginx/sites-available/your-app.conf

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    return 301 https://$host$request_uri;
}

# HTTPS Server Block
server {
    listen 443 ssl http2;
    server_name your-domain.com www.your-domain.com;

    # SSL Configuration (obtained via Certbot)
    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;

    # Root directory for your Laravel application
    root /var/www/your-app/public;
    index index.php index.html index.htm;

    # 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";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; # Ensure you have valid SSL for all subdomains

    # Cache static assets for a year
    location ~* \.(?:css|js|jpg|jpeg|gif|png|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;
    }

    # Handle all other requests to Laravel's front controller
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Pass PHP scripts to PHP-FPM
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Adjust socket path based on your PHP-FPM configuration
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Example for PHP 8.1
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Prevent access to sensitive files
    location ~ ^/(composer\.json|composer\.lock|storage|bootstrap/cache|\.env) {
        deny all;
    }

    # Optimize PHP-FPM buffer sizes if needed
    # fastcgi_buffers 8 16k;
    # fastcgi_buffer_size 32k;
}

After creating the file, enable the site and test the configuration:

sudo ln -s /etc/nginx/sites-available/your-app.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

PHP-FPM Tuning for Laravel

PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications like Laravel. Its performance is critical. We’ll tune its process management and resource allocation.

PHP-FPM Configuration Files

The primary configuration file is usually /etc/php/[version]/fpm/php-fpm.conf. Pool configurations are in /etc/php/[version]/fpm/pool.d/www.conf (or a custom pool name).

Process Management: Dynamic vs. Static

The most impactful setting is the process manager. For most Laravel applications on Linode, the dynamic process manager is a good balance. It scales the number of FPM workers based on demand, saving resources when idle.

Key directives in pool.d/www.conf:

  • pm = dynamic: Use dynamic process management. Other options are static (fixed number of workers) and ondemand (spawns workers only when a request comes in).
  • pm.max_children: The maximum number of FPM child processes that can be spawned. This is the most critical setting. It should be calculated based on your server’s RAM and the memory footprint of your Laravel application. A common formula is (Total RAM - RAM for OS/Nginx/DB) / Average PHP Process Memory.
  • pm.start_servers: The number of FPM processes to start when the FPM master process is started.
  • pm.min_spare_servers: The minimum number of FPM processes to keep idle.
  • pm.max_spare_servers: The maximum number of FPM processes to keep idle.
  • pm.process_idle_timeout: The number of seconds after which an idle process will be killed.
  • pm.max_requests: The number of requests each child process should execute before respawning. This helps prevent memory leaks. A value between 500 and 1000 is typical.

Example pool.d/www.conf Tuning

; /etc/php/8.1/fpm/pool.d/www.conf (Example for PHP 8.1)

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock ; Ensure this matches Nginx config
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 100      ; Adjust based on server RAM and app memory usage
pm.start_servers = 10      ; Initial number of workers
pm.min_spare_servers = 5   ; Minimum idle workers
pm.max_spare_servers = 20  ; Maximum idle workers
pm.max_requests = 750      ; Respawn after this many requests
pm.process_idle_timeout = 10s ; Kill idle processes after 10 seconds

; Other useful settings
request_terminate_timeout = 60s ; Timeout for individual PHP scripts
; rlimit_files = 4096 ; Increase if needed, often handled by OS limits
; rlimit_core = 0 ; Disable core dumps for production

After modifying the PHP-FPM pool configuration, restart the service:

sudo systemctl restart php8.1-fpm # Adjust version as needed

Monitoring PHP-FPM Memory Usage

Use tools like htop or ps aux | grep php-fpm to monitor the memory consumption of your PHP-FPM processes. If your server is constantly swapping or running out of memory, you need to reduce pm.max_children. If you have plenty of free RAM and your application feels sluggish under load, you might be able to increase it.

MongoDB Performance Tuning on Linode

MongoDB’s performance is heavily influenced by its configuration, hardware, and workload. On Linode, you have control over these factors. We’ll focus on key configuration parameters and operational best practices.

Key MongoDB Configuration Parameters

The main configuration file is typically /etc/mongod.conf.

Storage Engine

Ensure you are using the WiredTiger storage engine, which is the default and recommended for most workloads. It offers excellent compression and concurrency.

# /etc/mongod.conf
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
  engine: wiredTiger # Explicitly set, though usually default
  wiredTiger:
    engineConfig:
      cacheSizeGB: 0.75 # Allocate 75% of available RAM to WiredTiger cache
    collectionConfig:
      blockSize: 64KB # Default, adjust if specific needs arise
    indexConfig:
      prefixCompression: true

cacheSizeGB: This is arguably the most critical setting. Allocate a significant portion of your Linode instance’s RAM to the WiredTiger cache. A common recommendation is 50-75% of available RAM. For a 4GB Linode, you might set this to 2 or 3 GB. Monitor your system’s RAM usage; if MongoDB is consuming too much and causing swapping, reduce this value.

Network and Connections

Configure the network interface and maximum connections.

# /etc/mongod.conf
net:
  port: 27017
  bindIp: 0.0.0.0 # Or specific IP if restricting access
  maxIncomingConnections: 2000 # Adjust based on expected load and server resources

maxIncomingConnections: Set this to a value that can accommodate your expected concurrent connections. Ensure your Linode’s firewall and MongoDB’s access control are properly configured.

Logging

Configure logging for easier debugging and performance analysis.

# /etc/mongod.conf
systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true
  verbosity: 0 # 0 for normal, higher for more verbose logging (e.g., for debugging)
  quiet: false
  component:
    storage:
      logLevel: 0
    command:
      logLevel: 0
    index:
      logLevel: 0
    query:
      logLevel: 0

Query and Index Optimization

This is crucial for application performance. MongoDB’s query optimizer relies on indexes to efficiently retrieve data. Poorly indexed queries are a common bottleneck.

Identifying Slow Queries

Enable the slow query log in MongoDB. Set a threshold (e.g., 100ms) for queries to be logged.

# /etc/mongod.conf
operationProfiling:
  mode: "slowOp" # or "all" for profiling all operations
  slowOpThresholdMs: 100 # Log operations slower than 100ms

After applying this change, restart MongoDB:

sudo systemctl restart mongod

Analyze the MongoDB logs (/var/log/mongodb/mongod.log) for slow operations. Use the explain() method in the MongoDB shell to understand query execution plans and identify missing indexes.

Example: Using explain()

// Connect to your MongoDB instance
// mongo

// Example query that might be slow
db.users.find({ email: "[email protected]" })

// Explain the query execution plan
db.users.find({ email: "[email protected]" }).explain("executionStats")

Look for:

  • totalKeysExamined vs. totalDocsExamined: Ideally, these should be close. A large difference indicates inefficient scanning.
  • winningPlan.stage: Should ideally be IXSCAN (index scan) rather than COLLSCAN (collection scan).

If a COLLSCAN is present or totalKeysExamined is high, you likely need an index. For the example above, an index on the email field would be beneficial:

db.users.createIndex({ email: 1 })

Monitoring and Maintenance

Regularly monitor MongoDB’s performance using tools like:

  • mongostat: Provides real-time server statistics.
  • mongotop: Shows per-collection read/write activity.
  • Linode’s built-in monitoring for CPU, RAM, and disk I/O.

Perform regular backups and consider periodic index rebuilding or repair if you encounter data corruption issues (though this is rare with WiredTiger).

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala