11. Hardening and Optimizing the LEMP Stack
11.1. Introduction to Hardening and Optimization
Section titled “11.1. Introduction to Hardening and Optimization”After installing the LEMP stack, it’s essential to harden and optimize each component to ensure maximum security and performance. This section covers advanced configurations for Nginx, MariaDB, and PHP-FPM that will:
- Improve security by reducing attack surfaces
- Enhance performance for handling WordPress traffic
- Optimize resource usage on your server
- Prepare your server for production workloads
11.2. Optimizing Nginx Configuration
Section titled “11.2. Optimizing Nginx Configuration”Nginx is highly configurable and can be fine-tuned for better performance and security. We’ll organize our configuration into modular files for easier management.
11.2.1. Preparation
Section titled “11.2.1. Preparation”First, let’s create a backup of the original configuration and set up a directory structure for our modular configuration files:
# Navigate to Nginx configuration directorycd /etc/nginx/
# Create an includes directory for modular configurationsudo mkdir includes/
# Create a backup of the original configurationsudo cp nginx.conf nginx.conf.bak
# Edit the main configuration filesudo vim nginx.conf11.2.2. Main Context Optimization
Section titled “11.2.2. Main Context Optimization”Add the following directives to the main context (outside any blocks) in your nginx.conf file:
# Increase the maximum number of open files per workerworker_rlimit_nofile 30000;
# Set worker processes to higher priority (-20 to 19, lower is higher priority)worker_priority -10;
# Optimize internal timer resolutiontimer_resolution 100ms;
# Enable PCRE JIT compilation for better regex performancepcre_jit on;These settings:
- Increase the file descriptor limit for Nginx workers
- Give Nginx workers higher CPU priority
- Optimize timer resolution for better performance
- Enable Just-In-Time compilation for regular expressions
11.2.3. Events Context Optimization
Section titled “11.2.3. Events Context Optimization”In the events {} block, modify or add these directives:
events { # Increase maximum connections per worker worker_connections 4096;
# Enable accept mutex for better connection distribution accept_mutex on;
# Set accept mutex delay accept_mutex_delay 200ms;
# Use the efficient epoll event model on Linux use epoll;}These settings optimize how Nginx handles connections:
- Increases the number of simultaneous connections each worker can handle
- Improves connection distribution among worker processes
- Uses the efficient epoll event processing method available on Linux
11.2.4. Creating Modular Configuration Files
Section titled “11.2.4. Creating Modular Configuration Files”Let’s create separate configuration files for different aspects of Nginx:
# Navigate to the includes directorycd /etc/nginx/includes/
# Create configuration files for different aspectssudo touch basic_settings.conf buffers.conf timeouts.conf file_handle_cache.conf gzip.conf brotli.conf
# List the files to confirm creationls -l11.2.5. Basic Settings Configuration
Section titled “11.2.5. Basic Settings Configuration”Create the basic settings configuration file:
# Edit the basic settings configurationsudo vim /etc/nginx/includes/basic_settings.confAdd the following content:
### BASIC SETTINGS### Set character encodingcharset utf-8;
# Enable efficient file servingsendfile on;sendfile_max_chunk 512k;
# Optimize TCP settingstcp_nopush on;tcp_nodelay on;
# Security: Hide server informationserver_tokens off;more_clear_headers 'Server';more_clear_headers 'X-Powered';
# Server name handlingserver_name_in_redirect off;server_names_hash_bucket_size 64;
# Hash table settingsvariables_hash_max_size 2048;types_hash_max_size 2048;
# MIME typesinclude /etc/nginx/mime.types;default_type application/octet-stream;These settings:
- Set UTF-8 as the default character encoding
- Enable efficient file serving with sendfile
- Optimize TCP for better performance
- Hide server information for improved security
- Configure hash tables for better performance
11.2.6. Buffer Settings Configuration
Section titled “11.2.6. Buffer Settings Configuration”Create the buffer settings configuration file:
# Edit the buffers configurationsudo vim /etc/nginx/includes/buffers.confAdd the following content:
### BUFFERS### Client request buffersclient_body_buffer_size 256k;client_body_in_file_only off;client_header_buffer_size 64k;client_max_body_size 100m; # Consider reducing after setup
# Connection and I/O buffersconnection_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;These buffer settings:
- Optimize memory usage for client requests
- Set appropriate buffer sizes for headers and body content
- Configure I/O operations for better performance
- Set a reasonable maximum body size for file uploads
11.2.7. Timeout Settings Configuration
Section titled “11.2.7. Timeout Settings Configuration”Create the timeout settings configuration file:
# Edit the timeouts configurationsudo vim /etc/nginx/includes/timeouts.confAdd the following content:
### TIMEOUTS### Connection timeoutskeepalive_timeout 5;keepalive_requests 500;lingering_time 20s;lingering_timeout 5s;keepalive_disable msie6;
# Reset timed out connectionsreset_timedout_connection on;
# Request timeoutssend_timeout 15s;client_header_timeout 8s;client_body_timeout 10s;These timeout settings:
- Optimize connection handling and resource usage
- Prevent slow clients from consuming server resources
- Close idle connections after 5 seconds
- Allow up to 500 requests per connection before closing
11.2.8. Compression Settings
Section titled “11.2.8. Compression Settings”Gzip Configuration
Section titled “Gzip Configuration”Create the Gzip compression configuration file:
# Edit the gzip configurationsudo vim /etc/nginx/includes/gzip.confAdd the following content:
### GZIP### Enable gzip compressiongzip on;gzip_vary on;gzip_disable "MSIE [1-6]\.";gzip_static on;
# Gzip settingsgzip_min_length 1400;gzip_buffers 32 8k;gzip_http_version 1.0;gzip_comp_level 5;gzip_proxied any;
# MIME types to compressgzip_types text/plain text/css text/xml application/javascript application/x-javascript application/xml application/xml+rss application/ecmascript application/json image/svg+xml;Brotli Configuration
Section titled “Brotli Configuration”Create the Brotli compression configuration file:
# Edit the brotli configurationsudo vim /etc/nginx/includes/brotli.confAdd the following content:
### BROTLI### Enable Brotli compressionbrotli on;brotli_comp_level 6;brotli_static on;
# MIME types to compress with Brotlibrotli_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:
- Reduces bandwidth usage by 70-90% for text-based content
- Improves page load times for visitors
- Brotli typically achieves 15-25% better compression than Gzip
11.2.9. File Handle Cache Configuration
Section titled “11.2.9. File Handle Cache Configuration”Create the file handle cache configuration file:
# Edit the file handle cache configurationsudo vim /etc/nginx/includes/file_handle_cache.confAdd the following content:
### FILE HANDLE CACHE### Cache open file descriptors and informationopen_file_cache max=50000 inactive=60s;open_file_cache_valid 120s;open_file_cache_min_uses 2;open_file_cache_errors off;These settings:
- Cache information about open files to reduce disk I/O
- Store up to 50,000 file entries in the cache
- Remove entries not accessed for 60 seconds
- Validate cache entries every 120 seconds
- Require at least 2 uses before caching a file
11.2.10. Integrating Configuration Files into Nginx
Section titled “11.2.10. Integrating Configuration Files into Nginx”Now that we’ve created all our modular configuration files, we need to integrate them into the main Nginx configuration file.
First, let’s edit the main configuration file:
# Edit the main Nginx configuration filesudo vim /etc/nginx/nginx.confRemoving Default Configuration Blocks
Section titled “Removing Default Configuration Blocks”In the http {} block of your nginx.conf file, you’ll need to remove or comment out the following default sections:
### Basic Settings##
### SSL Settings##
### Logging Settings##
### Gzip Settings##Adding Include Directives
Section titled “Adding Include Directives”Replace the removed sections with include directives to our modular configuration files. Your http {} block should look similar to this:
http { # Basic Settings include /etc/nginx/includes/basic_settings.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;
# SSL Settings # (Keep any existing SSL settings here)
# Logging Settings access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log;
# Compression Settings include /etc/nginx/includes/gzip.conf; include /etc/nginx/includes/brotli.conf;
# Virtual Host Configs include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;}This modular approach makes your configuration:
- Easier to maintain and update
- More organized and readable
- Simpler to troubleshoot when issues arise
11.2.11. Testing and Applying the Configuration
Section titled “11.2.11. Testing and Applying the Configuration”After making all changes, it’s essential to test the configuration before applying it:
# Test the Nginx configuration for syntax errorssudo nginx -tIf the test is successful, reload Nginx to apply the changes:
# Reload Nginx with the new configurationsudo systemctl reload nginx11.2.12. Verifying File Descriptor Limits
Section titled “11.2.12. Verifying File Descriptor Limits”To ensure our file descriptor limit settings are applied correctly:
# Find Nginx worker processesps aux | grep nginx
# Check the file descriptor limits for a worker process# Replace XXXX with the actual process ID from the previous commandsudo cat /proc/XXXX/limits | grep "open files"If you need to increase the file descriptor limit further, you can modify the worker_rlimit_nofile directive in the main context of nginx.conf:
# Edit nginx.conf again if neededsudo vim /etc/nginx/nginx.confIncrease the value if necessary:
# Increase file descriptor limit if neededworker_rlimit_nofile 45000;After any changes, test and reload Nginx again:
# Test configurationsudo nginx -t
# Reload Nginxsudo systemctl reload nginx11.3. Creating Useful Bash Aliases
Section titled “11.3. Creating Useful Bash Aliases”Bash aliases can save you time by creating shortcuts for frequently used commands. This is especially helpful when managing your server.
11.3.1. Setting Up Server Management Aliases
Section titled “11.3.1. Setting Up Server Management Aliases”Create or edit your .bash_aliases file:
# Edit your bash aliases filevim ~/.bash_aliasesAdd the following useful aliases for server management:
# System update and maintenancealias server_update='sudo apt update && sudo apt upgrade && sudo apt autoremove'
# Nginx managementalias ngt='sudo nginx -t' # Test Nginx configurationalias ngr='sudo systemctl reload nginx' # Reload Nginxalias ngsa='cd /etc/nginx/sites-available/ && ls' # Navigate to sites-availablealias ngin='cd /etc/nginx/includes/ && ls' # Navigate to includes directory
# PHP-FPM managementalias fpmr='sudo systemctl restart php8.3-fpm' # Restart PHP-FPM11.3.2. Activating Your Aliases
Section titled “11.3.2. Activating Your Aliases”To make your aliases available in your current session:
# Source your bash aliases filesource ~/.bash_aliasesTo test that your aliases are working:
# Test the nginx configuration using the aliasngt11.4. Optimizing and Securing MariaDB
Section titled “11.4. Optimizing and Securing MariaDB”MariaDB is the database server that stores all your WordPress data. Properly configuring it is crucial for both security and performance.
11.4.1. Securing MariaDB Installation
Section titled “11.4.1. Securing MariaDB Installation”First, run the MariaDB secure installation script to remove insecure defaults and lock down access:
# Run the security scriptsudo mysql_secure_installation11.4.2. Understanding the Security Script Options
Section titled “11.4.2. Understanding the Security Script Options”When running the mysql_secure_installation script, you’ll be presented with several security options. Here’s what each option means and the recommended settings:
-
Root Password: If this is your first time running the script, you’ll be asked to set a root password. Choose a strong password and store it securely.
-
Unix Socket Authentication: This option allows you to authenticate using the Unix socket file instead of a password. For most WordPress setups, you can answer
n. -
Change Root Password: If you’ve already set a root password, you can choose to change it or keep it by answering
n. -
Remove Anonymous Users: Anonymous users are a security risk and should be removed in production environments. Answer
Y. -
Disallow Root Login Remotely: Preventing remote root login is a security best practice. Answer
Y. -
Remove Test Database: The test database is not needed in production and should be removed. Answer
Y. -
Reload Privilege Tables: This applies all changes immediately. Answer
Y.
11.4.3. Optimizing MariaDB Performance
Section titled “11.4.3. Optimizing MariaDB Performance”After securing MariaDB, let’s optimize its performance for WordPress. First, create a backup of the configuration file:
# Navigate to the MariaDB configuration directorycd /etc/mysql/mariadb.conf.d/
# Create a backup of the server configurationsudo cp 50-server.cnf 50-server.cnf.bak
# Edit the configuration filesudo vim 50-server.cnfEnabling the Performance Schema
Section titled “Enabling the Performance Schema”The Performance Schema provides instrumentation to help monitor MariaDB server execution at a low level. Add these lines to the [mysqld] section:
# Performance Schemaperformance_schema=ONperformance-schema-instrument='stage/%=ON'performance-schema-consumer-events-stages-current=ONperformance-schema-consumer-events-stages-history=ONperformance-schema-consumer-events-stages-history-long=ONDisabling DNS Lookups
Section titled “Disabling DNS Lookups”Add this line to improve connection speed by preventing DNS lookups:
# Disable DNS lookups for client connectionsskip-name-resolveThis prevents the server from attempting to resolve client hostnames, which can slow down connections, especially when DNS is slow or unavailable.
Configuring Query Cache
Section titled “Configuring Query Cache”MariaDB includes a query cache feature that can improve performance for read-heavy workloads by caching the results of SELECT queries. You can check the current query cache settings:
# Log into MariaDBsudo mysql -u root -p
# Check if query cache is availableMariaDB [(none)]> show variables like 'have_query_cache';
# Check query cache settingsMariaDB [(none)]> show variables like 'query_cache%';For WordPress sites with moderate traffic, you can enable and configure the query cache by adding these lines to the [mysqld] section:
# Query Cache Configurationquery_cache_type = 1query_cache_size = 16Mquery_cache_limit = 1MOptimizing Binary Logs
Section titled “Optimizing Binary Logs”Binary logs record all changes to your database and are used for replication and point-in-time recovery. By default, these logs are kept for 10 days, which can consume significant disk space. You can check and modify the retention period:
# Check current binary log expiration settingMariaDB [(none)]> show variables like 'expire_logs_days';
# Set binary logs to expire after 3 daysMariaDB [(none)]> set global expire_logs_days = 3;
# Verify the changeMariaDB [(none)]> show variables like 'expire_logs_days';
# Flush binary logs to apply changesMariaDB [(none)]> flush binary logs;To make this change permanent, add this line to the [mysqld] section of your MariaDB configuration:
# Binary Log Settingsexpire_logs_days = 3Optimizing InnoDB Buffer Pool
Section titled “Optimizing InnoDB Buffer Pool”InnoDB is the default storage engine for MariaDB and is optimized for performance and reliability. The buffer pool is one of the most important settings to tune for performance:
# Check current InnoDB buffer pool settingsMariaDB [(none)]> show variables like '%innodb_buffer%';
# Check InnoDB log file settingsMariaDB [(none)]> show variables like '%innodb_log%';For optimal performance, add these settings to the [mysqld] section of your MariaDB configuration:
# InnoDB Settingsinnodb_buffer_pool_size = 800Minnodb_log_file_size = 200MAfter making these changes, restart MariaDB to apply them:
# Restart MariaDBsudo systemctl restart mariadbAdvanced InnoDB Optimization
Section titled “Advanced InnoDB Optimization”For larger servers with more RAM, you can further optimize InnoDB:
# Advanced InnoDB settings for servers with 8GB+ RAMinnodb_buffer_pool_size = 2048Minnodb_log_file_size = 512M11.4.4. Using MySQLTuner for Advanced Optimization
Section titled “11.4.4. Using MySQLTuner for Advanced Optimization”MySQLTuner is a Perl script that analyzes your MariaDB performance and provides recommendations for improving it:
# Download MySQLTunerwget http://mysqltuner.pl/ -O mysqltuner.pl
# Make it executablechmod +x mysqltuner.pl
# Run MySQLTuner (best after server has been running for at least 24 hours)sudo ./mysqltuner.plMySQLTuner will analyze your current configuration and usage patterns, then provide specific recommendations for optimizing your MariaDB installation. This is especially valuable after your WordPress site has been running for some time, as it can identify bottlenecks based on actual usage.
11.4.5. Increasing MariaDB File Descriptor Limits
Section titled “11.4.5. Increasing MariaDB File Descriptor Limits”MariaDB, like other services, is limited by the number of open files it can have. For busy databases, the default limits may be too low:
# Check current file descriptor limits for MariaDBps aux | grep mysqlcat /proc/$(ps aux | grep mysql | grep -v grep | awk '{print $2}')/limits | grep "Max open files"To increase the file descriptor limits for MariaDB:
# Navigate to systemd directorycd /etc/systemd/system/
# Create a directory for MariaDB service overrides if it doesn't existsudo mkdir -p mariadb.service.d/
# Navigate to the new directorycd mariadb.service.d/
# Create a limits configuration filesudo nano limits.confAdd the following to the limits.conf file:
[Service]LimitNOFILE=40000Apply the changes:
# Reload systemd to recognize the changessudo systemctl daemon-reload
# Restart MariaDBsudo systemctl restart mariadb
# Verify the new limitscat /proc/$(ps aux | grep mysql | grep -v grep | awk '{print $2}')/limits | grep "Max open files"You should see that the “Max open files” value has been increased to 40000, which will allow MariaDB to handle more concurrent connections and operations.
11.5. Hardening and Optimizing PHP-FPM
Section titled “11.5. Hardening and Optimizing PHP-FPM”PHP-FPM (FastCGI Process Manager) is the PHP implementation that works with Nginx. Properly configuring PHP-FPM is essential for both security and performance.
11.5.1. PHP Security and Performance Settings
Section titled “11.5.1. PHP Security and Performance Settings”Edit the PHP configuration file:
# Find the main PHP configuration filesudo find /etc/php/8.3/ -name php.ini
# Edit the PHP configuration file (adjust path if needed)sudo vim /etc/php/8.3/fpm/php.iniAdd or modify the following settings:
; Security Settingsallow_url_fopen = Off ; Prevents PHP from opening remote filescgi.fix_pathinfo = 0 ; Prevents path traversal vulnerabilitiesexpose_php = Off ; Hides PHP version in HTTP headers
; Performance Settingsupload_max_filesize = 100M ; Maximum allowed size for uploaded filespost_max_size = 125M ; Maximum size of POST data (should be larger than upload_max_filesize)max_input_vars = 3000 ; Maximum input variables (important for complex WordPress forms)memory_limit = 256M ; Maximum memory a script can consume
; Resource Limitsrlimit_files = 32768 ; Maximum number of open filesrlimit_core = unlimited ; Allow core dumps for debugging11.5.2. PHP-FPM Pool Configuration
Section titled “11.5.2. PHP-FPM Pool Configuration”For better performance, you can also optimize the PHP-FPM pool configuration:
# Edit the www.conf filesudo vim /etc/php/8.3/fpm/pool.d/www.confFind and modify these settings:
; Process Manager Settingspm = dynamic ; Dynamic process managerpm.max_children = 50 ; Maximum number of child processespm.start_servers = 5 ; Number of child processes created at startuppm.min_spare_servers = 5 ; Minimum number of idle server processespm.max_spare_servers = 35 ; Maximum number of idle server processespm.max_requests = 500 ; Number of requests each child process should execute before respawning11.5.3. Applying the Changes
Section titled “11.5.3. Applying the Changes”After making these changes, restart PHP-FPM and reload Nginx:
# Restart PHP-FPMsudo systemctl restart php8.3-fpm
# Reload Nginxsudo systemctl reload nginx11.6. Conclusion
Section titled “11.6. Conclusion”You have now successfully hardened and optimized your LEMP stack (Linux, Nginx, MariaDB, and PHP) for WordPress hosting. These configurations provide a good balance of security and performance for most WordPress sites.
Remember to monitor your server’s performance and adjust these settings as needed based on your specific workload and traffic patterns. Tools like MySQLTuner, Nginx status pages, and PHP-FPM status pages can help you identify bottlenecks and fine-tune your configuration.