NGINX Hardening and Optimization

Section 11: Complete Configuration Guide

📋 Overview

In this section, we will harden and optimize NGINX for production WordPress environments. The default NGINX configuration is already secure and fairly well optimized, making it straightforward to enhance performance and security with targeted modifications.

⚠️ Important Note

There is no one-size-fits-all configuration for all sites. You need to configure NGINX based on the specific requirements of your applications. This guide focuses on optimizing NGINX for WordPress sites.

📁 NGINX Configuration Architecture

Main Context (Global Settings)

Controls fundamental NGINX behavior affecting all operations

Events Context (Connection Handling)

Manages how NGINX handles client connections

HTTP Context (Web Server Settings)

Contains all HTTP-related configurations including:

  • Basic Settings
  • Buffer Configurations
  • Timeout Settings
  • Compression (Gzip/Brotli)
  • File Handle Cache

Server Context (Virtual Hosts)

Individual site configurations (covered later in the course)

🛠️ Initial Setup: Creating Include Files

NGINX uses the include directive to modularize configurations, making them more manageable and reusable across multiple sites.

Configuration Organization Flow

Create Includes Directory
Create Empty Config Files
Add Directives
Include in nginx.conf

Step 1: Backup Original Configuration

cd /etc/nginx/
sudo cp nginx.conf nginx.conf.bak

Step 2: Create Includes Directory

sudo mkdir includes/
cd includes/

Step 3: Create Configuration Files

sudo touch basic_settings.conf buffers.conf timeouts.conf \
           file_handle_cache.conf gzip.conf brotli.conf

⚙️ Main Context Configuration

Main Context Directives

The Main Context is the only context without curly brackets. It controls process-level settings for NGINX.

worker_rlimit_nofile 30000;
worker_priority -10;
timer_resolution 100ms;
pcre_jit on;
  • worker_rlimit_nofile 30000; Sets the maximum number of open file descriptors per worker process. This allows NGINX to handle more simultaneous connections and efficiently manage I/O operations.
  • worker_priority -10; Assigns higher priority to NGINX worker processes (lower values = higher priority). This ensures NGINX processes are scheduled promptly by the kernel, especially under high load.
  • timer_resolution 100ms; Sets timer resolution to 100 milliseconds, affecting timeout handling and event scheduling precision.
  • pcre_jit on; Enables Just-In-Time compilation for Perl Compatible Regular Expressions, improving regex processing performance.

🔄 Events Context Configuration

Events Context Directives

The Events Context configures how NGINX handles connections at the process level.

events {
    worker_connections 4096;
    accept_mutex on;
    accept_mutex_delay 200ms;
    use epoll;
}
  • worker_connections 4096; Maximum simultaneous connections per worker process. With multiple workers, total capacity = workers × connections.
  • accept_mutex on; Synchronizes accept() calls among workers to prevent the "thundering herd" problem and distribute connections evenly.
  • accept_mutex_delay 200ms; Delays accepting new connections by 200ms when mutex is enabled, improving load distribution.
  • use epoll; Specifies the Linux epoll event notification mechanism for efficient handling of large numbers of connections.

Connection Handling Flow

Client Connection
Accept Mutex
Worker Selection
Epoll Processing
Request Handling

📝 Basic Settings Configuration

File: /etc/nginx/includes/basic_settings.conf

##
# BASIC SETTINGS
## 
charset utf-8;
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
more_clear_headers 'Server';
more_clear_headers 'X-Powered';
server_name_in_redirect off;
server_names_hash_bucket_size 64;
variables_hash_max_size 2048;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;

Key Directives Explained

Directive Purpose Security/Performance Impact
charset utf-8; Sets default character encoding Ensures proper text rendering
sendfile on; Uses kernel sendfile() system call ⚡ Improves static file serving performance
tcp_nopush on; Optimizes TCP packet transmission ⚡ Reduces network overhead
tcp_nodelay on; Disables Nagle's algorithm ⚡ Improves small packet performance
server_tokens off; Hides NGINX version number 🔒 Security hardening

💾 Buffer Configuration

File: /etc/nginx/includes/buffers.conf

Understanding NGINX Buffers

NGINX uses three types of buffers for efficient data handling:

  • Input Buffers: Store client request bodies before upstream processing
  • Output Buffers: Store response data before sending to clients
  • FastCGI Buffers: Store responses from PHP-FPM or other FastCGI servers
##
# BUFFERS
## 
client_body_buffer_size 256k;
client_body_in_file_only off;
client_header_buffer_size 64k;
client_max_body_size 100m;
connection_pool_size 512;
directio 4m;
ignore_invalid_headers on;
large_client_header_buffers 8 64k;
output_buffers 8 256k;
postpone_output 1460;
request_pool_size 32k;

Buffer Directives Breakdown

⏱️ Timeout Configuration

File: /etc/nginx/includes/timeouts.conf

##
# TIMEOUTS
## 
keepalive_timeout 5;
keepalive_requests 500;
lingering_time 20s;
lingering_timeout 5s;
keepalive_disable msie6;
reset_timedout_connection on;
send_timeout 15s;
client_header_timeout 8s;
client_body_timeout 10s;

Request Timeout Flow

client_header_timeout (8s) → Client must send headers within 8 seconds

client_body_timeout (10s) → Client must send request body within 10 seconds

send_timeout (15s) → Response must be sent within 15 seconds

keepalive_timeout (5s) → Keep connection alive for 5 seconds for reuse

Timeout Directives Explained

Directive Value Purpose
keepalive_timeout 5 seconds How long to keep idle connections open
keepalive_requests 500 Max requests per keepalive connection
reset_timedout_connection on Reset timed-out connections to free resources
send_timeout 15 seconds Timeout for transmitting response to client
client_header_timeout 8 seconds Timeout for reading client request header
client_body_timeout 10 seconds Timeout for reading client request body

🗜️ Compression Configuration

File: /etc/nginx/includes/gzip.conf

##
# GZIP
## 
gzip on;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
gzip_static on;
gzip_min_length 1400;
gzip_buffers 32 8k;
gzip_http_version 1.0;
gzip_comp_level 5;
gzip_proxied any;
gzip_types text/plain text/css text/xml application/javascript 
           application/x-javascript application/xml 
           application/xml+rss application/ecmascript 
           application/json image/svg+xml;

File: /etc/nginx/includes/brotli.conf

##
# BROTLI
## 
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_types application/atom+xml application/javascript 
             application/json application/rss+xml 
             application/vnd.ms-fontobject application/x-font-opentype 
             application/x-font-truetype application/x-font-ttf 
             application/x-javascript application/xhtml+xml 
             application/xml font/eot font/opentype font/otf 
             font/truetype image/svg+xml image/vnd.microsoft.icon 
             image/x-icon image/x-win-bitmap text/css 
             text/javascript text/plain text/xml;

Compression Benefits

Gzip: Widely supported compression reducing bandwidth by 70-90% for text files

Brotli: Modern compression with 15-25% better compression than Gzip, supported by all modern browsers

📂 File Handle Cache Configuration

File: /etc/nginx/includes/file_handle_cache.conf

##
# FILE HANDLE CACHE
## 
open_file_cache max=50000 inactive=60s;
open_file_cache_valid 120s;
open_file_cache_min_uses 2;
open_file_cache_errors off;

File Handle Cache Explained

🔗 Updating nginx.conf

Include All Configuration Files

After creating all include files, update /etc/nginx/nginx.conf to reference them:

http {
    ##
    # Basic Settings
    ##
    include /etc/nginx/includes/basic_settings.conf;
    
    ##
    # Gzip Settings
    ##
    include /etc/nginx/includes/gzip.conf;
    
    ##
    # Brotli Settings
    ##
    include /etc/nginx/includes/brotli.conf;
    
    ##
    # Buffer Settings
    ##
    include /etc/nginx/includes/buffers.conf;
    
    ##
    # Timeout Settings
    ##
    include /etc/nginx/includes/timeouts.conf;
    
    ##
    # File Handle Cache Settings
    ##
    include /etc/nginx/includes/file_handle_cache.conf;
    
    # ... rest of configuration
}

✅ Testing and Applying Configuration

Test NGINX Configuration

sudo nginx -t

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload NGINX

sudo systemctl reload nginx

🔍 Verifying Open File Limits

Check NGINX Process Limits

# Find NGINX worker process ID
ps aux | grep www-data

# Check limits (replace XXX with actual PID)
cat /proc/XXX/limits

Verify that "Max open files" shows the configured limit (30000 or higher).

Increasing Limits if Needed

If limits are lower than expected, update the main context:

worker_rlimit_nofile 45000;

Then reload NGINX and verify again.

⚡ Bash Aliases for Efficiency

Create Useful Aliases

# Edit bash aliases
nano ~/.bash_aliases

# Add these aliases:
alias server_update='sudo apt update && sudo apt upgrade && sudo apt autoremove'
alias ngt='sudo nginx -t'
alias ngr='sudo systemctl reload nginx'
alias fpmr='sudo systemctl restart php8.3-fpm'
alias ngsa='cd /etc/nginx/sites-available/ && ls'
alias ngin='cd /etc/nginx/includes/ && ls'

# Activate aliases
su andrew  # Switch to your user to reload shell

Alias Benefits

  • ngt - Quick NGINX configuration test
  • ngr - Fast NGINX reload
  • fpmr - Restart PHP-FPM service
  • ngsa - Navigate to sites-available
  • ngin - Navigate to includes directory

🗄️ MariaDB Optimization

Secure MariaDB Installation

sudo mysql_secure_installation

Performance Schema Configuration

Edit MariaDB configuration:

cd /etc/mysql/mariadb.conf.d/
sudo cp 50-server.cnf 50-server.cnf.bak
sudo nano 50-server.cnf

Add performance schema settings:

# Performance Schema
performance_schema=ON
performance-schema-instrument='stage/%=ON'
performance-schema-consumer-events-stages-current=ON
performance-schema-consumer-events-stages-history=ON
performance-schema-consumer-events-stages-history-long=ON

# Network optimization
skip-name-resolve

# Binary log retention
expire_logs_days = 3

InnoDB Buffer Pool Optimization

For a 1GB RAM server:

innodb_buffer_pool_size = 800M
innodb_log_file_size = 200M

Important: Stop MariaDB Before Changing InnoDB Settings

sudo systemctl stop mariadb
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# Make changes
sudo systemctl start mariadb

Verify InnoDB Settings

sudo mysql
SHOW VARIABLES LIKE '%innodb_buffer%';
SHOW VARIABLES LIKE '%innodb_log%';

MariaDB Open File Limits

# Create systemd override directory
cd /etc/systemd/system/
sudo mkdir mariadb.service.d/
cd mariadb.service.d/

# Create limits configuration
sudo nano limits.conf

Add the following:

[Service]
LimitNOFILE=40000

Apply changes:

sudo systemctl daemon-reload
sudo systemctl restart mariadb

# Verify
ps aux | grep mysql
cat /proc/XXX/limits  # Replace XXX with PID

🐘 PHP-FPM Optimization

PHP Configuration Override

cd /etc/php/8.3/fpm/conf.d/
sudo nano server_override.ini

Add security and optimization settings:

# HARDENING PHP
allow_url_fopen = Off
cgi.fix_pathinfo=0
expose_php = Off

# OPTIMIZE PHP
upload_max_filesize = 100M
post_max_size = 125M
max_input_vars = 3000
memory_limit = 256M

WordPress wp-config.php

Remember to also set memory limit in your WordPress wp-config.php file:

define('WP_MEMORY_LIMIT', '256M');

PHP-FPM Open File Limits

cd /etc/php/8.3/fpm/
sudo cp php-fpm.conf php-fpm.conf.bak
sudo nano php-fpm.conf

Find and set:

rlimit_files = 32768
rlimit_core = unlimited

Apply changes:

sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm

# Verify
ps aux | grep php-fpm
cat /proc/XXX/limits  # Replace XXX with PID

🔧 MySQLTuner for Performance Analysis

Install and Run MySQLTuner

cd ~
mkdir MySQLTuner/
cd MySQLTuner/
wget http://mysqltuner.pl/ -O mysqltuner.pl
chmod +x mysqltuner.pl

# Run the tuner
sudo ./mysqltuner.pl

MySQLTuner Benefits

MySQLTuner analyzes your MariaDB/MySQL installation and provides recommendations for:

  • Memory usage optimization
  • Query cache settings
  • InnoDB configuration
  • Security improvements
  • Performance tuning

📊 Configuration Summary

Component Key Optimizations Files Modified
NGINX Worker processes, buffers, compression, file cache nginx.conf + 6 include files
MariaDB InnoDB buffer pool, binary logs, performance schema 50-server.cnf
PHP-FPM Memory limits, upload sizes, security hardening server_override.ini, php-fpm.conf

🎯 Best Practices

  • Always backup configuration files before modifications
  • Test NGINX configuration with nginx -t before reloading
  • Monitor server performance after configuration changes
  • Adjust buffer sizes based on actual traffic patterns
  • Run MySQLTuner periodically to identify optimization opportunities
  • Keep documentation of custom configurations
  • Use bash aliases to streamline routine tasks
  • Verify file limits match configured values

🔗 Reference Links

📝 Final Checklist

Task Status
Backup original nginx.conf
Create includes directory
Configure main context
Configure events context
Create all include files
Update nginx.conf with includes
Test NGINX configuration
Reload NGINX service
Verify open file limits
Configure MariaDB
Configure PHP-FPM
Set up bash aliases