• 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 MySQL on Linode for PHP

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on Linode for PHP

Nginx Configuration for High-Traffic PHP Applications

Optimizing Nginx is paramount for serving PHP applications efficiently. We’ll focus on key directives that impact connection handling, caching, and static file serving. This configuration assumes a Linode environment with a typical LAMP/LEMP stack.

Worker Processes and Connections

The `worker_processes` directive dictates how many worker processes Nginx will spawn. Setting this to `auto` is generally recommended, allowing Nginx to determine the optimal number based on available CPU cores. The `worker_connections` directive sets the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024, but this can be increased based on your server’s RAM and expected load.

Example Nginx Configuration Snippet

# /etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096; # Increased from 1024 for higher concurrency
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    server_tokens off; # Hides Nginx version for security

    # ... other http configurations ...
}

Gzip Compression

Enabling Gzip compression significantly reduces the bandwidth required to transfer HTML, CSS, and JavaScript files, leading to faster page load times. Configure it to compress responses for text-based content.

Example Gzip Configuration

# /etc/nginx/nginx.conf or included conf file

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # Compression level (1-9, 6 is a good balance)
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_disable "msie6"; # Disable for older IE versions

Caching Strategies

Leveraging browser caching and Nginx’s proxy caching can offload significant load from your application servers. For static assets, set appropriate `Cache-Control` headers. For dynamic content, consider implementing Nginx’s FastCGI cache if your PHP application is suitable.

Browser Caching for Static Assets

# In your server block or a separate conf file for static assets

location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Nginx FastCGI Cache (for PHP-FPM)

This requires enabling the FastCGI cache module. Define a cache zone and then apply it to your PHP location. Be mindful of cache invalidation strategies.

# In http block
fastcgi_cache_path /var/cache/nginx/php_cache levels=1:2 keys_zone=php_cache:100m inactive=60m;
fastcgi_temp_path /var/tmp/nginx/fastcgi_temp; # Ensure this directory exists and is writable by nginx user

# In your server block, within the PHP location
location ~ \.php$ {
    # ... other fastcgi_pass and fastcgi_param directives ...

    fastcgi_cache php_cache;
    fastcgi_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
    fastcgi_cache_valid 404 1m;      # Cache 404s for 1 minute
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    add_header X-Cache-Status $upstream_cache_status; # Useful for debugging

    # Optional: Bypass cache for logged-in users or specific requests
    # fastcgi_cache_bypass $http_cookie;
    # fastcgi_no_cache $http_cookie;
}

Gunicorn/PHP-FPM Tuning for Performance

The application server (Gunicorn for Python, PHP-FPM for PHP) is the next critical layer. Tuning its worker processes and memory usage directly impacts how many requests your application can handle concurrently and its responsiveness.

PHP-FPM Configuration

PHP-FPM (FastCGI Process Manager) is the standard for running PHP applications with Nginx. The primary configuration file is typically located at /etc/php/[version]/fpm/php.ini and /etc/php/[version]/fpm/pool.d/www.conf.

`php.ini` Directives

; /etc/php/[version]/fpm/php.ini

memory_limit = 256M       ; Increase if your app consumes more memory
upload_max_filesize = 64M ; Adjust based on expected file uploads
post_max_size = 64M       ; Should be >= upload_max_filesize
max_execution_time = 60   ; Max time in seconds for script execution

`www.conf` (Process Management)

The pm (process manager) setting is crucial. dynamic is often a good balance, but ondemand can save resources if traffic is sporadic. static provides the most consistent performance but consumes more memory.

; /etc/php/[version]/fpm/pool.d/www.conf

; Choose one of: static, dynamic, ondemand
pm = dynamic

; If pm = dynamic
pm.max_children = 100      ; Max number of children at any one time
pm.start_servers = 5       ; Number of children when PM starts
pm.min_spare_servers = 2   ; Min number of idle children
pm.max_spare_servers = 10  ; Max number of idle children
pm.process_idle_timeout = 10s ; How long idle processes are kept alive

; If pm = ondemand
pm.max_children = 100
pm.process_idle_timeout = 10s ; Timeout for idle processes to be killed

; If pm = static
pm.max_children = 100      ; Fixed number of children

Tuning Tip: Start with pm.max_children set to a value that your server’s RAM can comfortably support when all processes are active. A common formula is (Total RAM - RAM for OS/Nginx/MySQL) / Average PHP Process Memory Usage. Monitor your server’s memory usage under load and adjust accordingly. Restart PHP-FPM after making changes: sudo systemctl restart php[version]-fpm.

Gunicorn Configuration (for Python WSGI Apps)

Gunicorn is a popular WSGI HTTP Server for Python. Its configuration heavily influences concurrency and resource utilization. We’ll focus on worker types and counts.

Worker Types

  • sync: The default, synchronous worker. Simple but can block under heavy load.
  • eventlet, gevent: Asynchronous workers that use green threads. Excellent for I/O-bound applications.
  • tornado: Uses Tornado’s asynchronous I/O loop.

Worker Count

A common recommendation for Gunicorn workers is (2 * number_of_cores) + 1. However, this is a starting point. For I/O-bound applications using async workers, you might need significantly more workers.

Example Gunicorn Command Line/Configuration

# Example using command line arguments
gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application

# Example using a Gunicorn configuration file (e.g., gunicorn_config.py)
# /etc/gunicorn.d/myapp.py

import multiprocessing

bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gevent" # Or "sync", "eventlet"
threads = 2 # If using sync worker class, threads can help with I/O
# timeout = 30 # Adjust if your requests take longer
# keepalive = 2 # Keep-alive connections
# accesslog = "/var/log/gunicorn/access.log"
# errorlog = "/var/log/gunicorn/error.log"

Tuning Tip: For Python applications, especially those with significant I/O (database queries, external API calls), using gevent or eventlet with a higher worker count than the CPU-bound formula suggests can yield better throughput. Monitor CPU and memory usage. If CPU is maxed out, reduce workers. If memory is the bottleneck, reduce workers or optimize application memory usage.

MySQL Performance Tuning on Linode

Database performance is often the bottleneck. Tuning MySQL involves adjusting buffer sizes, query cache settings, and connection handling. We’ll focus on key parameters in my.cnf.

Key `my.cnf` Parameters

Locate your MySQL configuration file, typically /etc/mysql/my.cnf, /etc/mysql/mysql.conf.d/mysqld.cnf, or similar. Always back up your configuration before making changes.

[mysqld]
# General Settings
user                    = mysql
pid-file                = /var/run/mysqld/mysqld.pid
socket                  = /var/run/mysqld/mysqld.sock
port                    = 3306
basedir                 = /usr
datadir                 = /var/lib/mysql
tmpdir                  = /tmp
lc_messages_dir         = /usr/share/mysql
lc_messages             = en
skip-external-locking

# InnoDB Settings (Crucial for performance)
innodb_buffer_pool_size = 1G  ; **Most important setting.** Set to 50-75% of available RAM if MySQL is the primary service.
innodb_log_file_size    = 256M ; Larger files can improve write performance but increase recovery time.
innodb_log_buffer_size  = 16M  ; Buffer for transaction logs.
innodb_flush_log_at_trx_commit = 1 ; Default (ACID compliant). Set to 2 for better performance at slight risk.
innodb_flush_method     = O_DIRECT ; Recommended for Linux to avoid double buffering.
innodb_file_per_table   = 1    ; Recommended for better space management.

# MyISAM Settings (If you still use MyISAM)
key_buffer_size         = 128M ; For MyISAM index caching.

# Connection Settings
max_connections         = 200  ; Adjust based on application needs and server resources.
thread_cache_size       = 16   ; Cache threads for reuse.
table_open_cache        = 2000 ; Cache open table file descriptors.
table_definition_cache  = 1000 ; Cache table definitions.

# Query Cache (Deprecated in MySQL 8.0, removed in 8.0.34. Use with caution or disable.)
# query_cache_type        = 1
# query_cache_size        = 64M
# query_cache_limit       = 2M

# Other important settings
sort_buffer_size        = 4M
read_buffer_size        = 2M
read_rnd_buffer_size    = 4M
join_buffer_size        = 4M
tmp_table_size          = 64M
max_heap_table_size     = 64M

Tuning `innodb_buffer_pool_size`

This is the memory area where InnoDB caches table data and indexes. A larger buffer pool significantly reduces disk I/O. On a dedicated database server, setting this to 70-80% of the total system RAM is common. On a mixed-use server (e.g., web + DB), be more conservative, perhaps 50%.

`innodb_flush_log_at_trx_commit`

Setting this to 1 (default) ensures that each transaction commit flushes the log buffer to disk, providing full ACID compliance. Setting it to 2 means the log buffer is written to the OS buffer on commit, and the OS flushes it to disk approximately once per second. This offers a significant performance boost for writes but carries a small risk of losing the last second of transactions if the server crashes.

Query Optimization and Indexing

While not strictly a configuration parameter, effective query optimization is critical. Use EXPLAIN to analyze slow queries and ensure appropriate indexes are in place. Regularly review slow query logs.

-- Example of enabling and analyzing slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
SET GLOBAL long_query_time = 2; -- Log queries taking longer than 2 seconds
SET GLOBAL log_queries_not_using_indexes = 'ON'; -- Optional: Log queries not using indexes

-- To analyze a query:
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';

Tuning Tip: After modifying my.cnf, always restart the MySQL service: sudo systemctl restart mysql. Monitor server performance using tools like htop, iotop, and MySQL’s own performance schema or status variables (e.g., SHOW GLOBAL STATUS LIKE 'Innodb%';) to validate the impact of your changes.

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

  • Step-by-Step: Diagnosing indexing lock conflicts and high CPU during bulk stock updates on DigitalOcean Servers
  • How to Debug and Fix memory leaks and socket exhaustion in daemon processes in Modern C++ Applications
  • Infrastructure as Code: Provisioning Secure PHP Clusters on DigitalOcean Using Terraform
  • Fixing Slow Largest Contentful Paint (LCP) caused by unoptimized database queries in Legacy Laravel Codebases Without Breaking API Contracts
  • An Auditor’s Checklist for Securing Laravel Backends on Google Cloud

Copyright © 2026 · Vinay Vengala