๐ Managing NGINX Logs
NGINX logs are essential for monitoring server activity, debugging issues, and analyzing traffic patterns. Log files provide detailed information about requests, errors, and server performance.
Accessing Log Files
Navigate to the NGINX log directory:
cd /var/log/nginxList all log files:
lsView a specific log file:
sudo cat log_file_name.logView logs with pagination (recommended for large files):
sudo less log_file_name.logless for better navigation in large log files. You can search within the file using /search_term and navigate with arrow keys.
โ๏ธ PHP-FPM Pool Configuration
PHP-FPM (FastCGI Process Manager) pools allow you to run multiple PHP applications with different configurations, users, and resource limits. This provides better isolation and security for multi-site hosting environments.
PHP-FPM Pool Architecture
Web Server
Process Manager
example.com
user: example
site2.com
user: site2
Creating a System User
First, create a dedicated user for the website:
sudo useradd usernameUser Group Configuration
Add users to appropriate groups for proper permissions:
sudo usermod -a -G username_group www-datasudo usermod -a -G www-data username_groupsudo usermod -a -G username_group $USERCreating a New Pool Configuration
Navigate to the PHP-FPM pool directory:
cd /etc/php/8.3/fpm/pool.d/List existing pool configurations:
lsCopy the default pool configuration as a template:
sudo cp www.conf example.com.confEdit the new pool configuration:
sudo nano example.com.confEssential Pool Configuration Parameters
; Pool name
[example]
; User and group for the pool
user = username
group = username
; Socket location for communication with NGINX
listen = /run/php/php8.3-fpm-example.com.sock
; Resource limits
rlimit_files = 15000
rlimit_core = 100
; PHP configuration
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.example.com.log
php_admin_flag[log_errors] = onCreating the PHP-FPM Log File
Create the log file:
sudo touch /var/log/fpm-php.example.com.logSet proper ownership:
sudo chown user:www-data /var/log/fpm-php.example.com.logSet appropriate permissions:
sudo chmod 660 /var/log/fpm-php.example.com.logVerifying Socket Configuration
Verify the socket path in your pool configuration:
sudo grep "listen = /" example.com.confUpdating NGINX Configuration
Edit your NGINX site configuration:
sudo nano /etc/nginx/sites-available/example.com.confUpdate the fastcgi_pass directive to use your new socket:
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;Testing and Applying Changes
Test NGINX configuration:
sudo nginx -tReload PHP-FPM and NGINX:
sudo systemctl reload php8.3-fpmsudo systemctl reload nginxAdvanced PHP Configuration
Enabling URL File Operations
php_admin_flag[allow_url_fopen] = onDisabling Dangerous Functions
For security, disable potentially dangerous PHP functions:
php_admin_value[disable_functions] = shell_exec, opcache_get_configuration, opcache_get_status, disk_total_space, diskfreespace, dl, exec, passthru, pclose, pcntl_alarm, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority, pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_waitpid, pcntl_wait, pcntl_wexitstatus, pcntl_wifcontinued, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, popen, posix_getpwuid, posix_kill, posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, show_source, systemConfiguring Temporary Directories
php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/
php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/Create and configure the temporary directory:
cd /var/www/example.com/sudo mkdir tmp/sudo chown username:username tmp/sudo chmod 770 tmp/Restricting File Access with open_basedir
Limit PHP file access to specific directories:
php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/๐ SSL/TLS Certificate Configuration
SSL/TLS certificates encrypt data transmitted between your server and clients, providing security and trust. Modern web browsers require HTTPS for many features and rank HTTPS sites higher in search results.
Installing Certbot
Update package lists:
sudo apt updateInstall Certbot with Cloudflare DNS plugin:
sudo apt install certbot python3-certbot-dns-cloudflareObtaining SSL Certificates
Request a certificate using webroot validation:
sudo certbot certonly --webroot -w /var/www/example.com/public_html/ -d example.com -d www.example.com--webroot method requires that your site is already accessible via HTTP. Certbot will place verification files in your webroot directory.
Generating Diffie-Hellman Parameters
Create SSL directory:
cd /etc/nginx/sudo mkdir ssl/cd ssl/Generate DH parameters (this may take several minutes):
sudo openssl dhparam -out dhparam.pem 2048Creating SSL Configuration Files
Site-Specific SSL Configuration
Create a configuration file for your site's certificates:
sudo nano /etc/nginx/ssl/ssl_certs_example.com.confssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;Global SSL Configuration
Create a global SSL configuration for all sites:
sudo nano /etc/nginx/ssl/ssl_all_sites.conf# SSL A+ RATING CONFIGURATION
# UPDATED: MAY 2025
# SSL caching and protocols
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 180m;
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:DHE-RSA-CHACHA20-POLY1305;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# DNS resolver (Cloudflare)
resolver 1.1.1.1 1.0.0.1;
resolver_timeout 15s;
ssl_session_tickets off;
# HSTS headers
add_header Strict-Transport-Security "max-age=31536000;" always;
# HTTP/3 and QUIC support
ssl_early_data on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
add_header x-quic 'H3';
quic_retry on;Configuring NGINX Server Blocks for HTTPS
HTTP to HTTPS Redirect
server {
listen 80;
server_name example.com www.example.com;
# Permanent redirect to HTTPS
return 301 https://example.com$request_uri;
}HTTPS Server Block (Primary Site)
server {
listen 443 ssl;
http2 on;
listen 443 quic reuseport;
http3 on;
server_name example.com www.example.com;
# Include SSL configurations
include /etc/nginx/ssl/ssl_certs_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
# Rest of your configuration...
}Additional HTTPS Server Blocks
For additional sites, omit the reuseport parameter:
listen 443 ssl;
http2 on;
listen 443 quic;
http3 on;Adding HTTP_HOST Parameter
In your PHP processing block, add:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}Testing HTTP/3 Configuration
Add this directive temporarily to prevent browser caching:
# TESTING ONLY - Remove after verification
add_header Cache-Control 'no-cache,no-store';Testing Configuration
Test NGINX syntax:
sudo nginx -tReload NGINX:
sudo systemctl reload nginxTest with curl commands:
curl -I http://example.comcurl -I https://example.comSSL Testing Resources
- SSL Labs: Test your SSL configuration at ssllabs.com (expect A+ rating)
- HTTP/3 Check: Verify HTTP/3 support at http3check.net
- Browser DevTools: Open Network tab, right-click on column headers, enable "Protocol" column to see "h3" for HTTP/3 connections
Certificate Management Commands
| Command | Description |
|---|---|
sudo certbot certificates |
List all certificates |
sudo certbot delete |
Delete a certificate |
sudo certbot renew |
Renew certificates due for renewal |
sudo certbot renew --force-renewal |
Force renewal of all certificates |
Automated Certificate Renewal
Edit the root crontab:
sudo crontab -eAdd these lines to renew certificates twice monthly:
# Renew certificates on the 14th and 28th of each month at 1:00 AM
00 1 14,28 * * certbot renew --force-renewal
# Reload NGINX after renewal at 2:00 AM
00 2 14,28 * * systemctl reload nginx๐ก๏ธ HTTP Security Headers
HTTP response headers are a crucial component of web security. They provide the browser with instructions on how to handle content, control security policies, and protect against various types of attacks. These headers are sent by the server in response to HTTP requests made by clients (typically web browsers).
HTTP Header Communication Flow
Browser sends request
to server
Server processes request
& adds headers
Server sends content
with security headers
Browser enforces
header policies
Understanding HTTP Header Types
HTTP headers contain metadata about connections, requested resources, and returned resources. There are several types of headers:
- General Headers: Apply to both requests and responses
- Request Headers: Sent by the client to the server
- Response Headers: Sent by the server to the client
- Entity Headers: Describe the content body
Essential Security Headers
1. X-XSS-Protection
This header configures the XSS (Cross-Site Scripting) auditor built into older browsers. When an XSS attack is detected, the browser can block the page or sanitize it.
| Value | Description |
|---|---|
0 |
Disables XSS filtering |
1 |
Enables XSS filtering (sanitizes page) |
1; mode=block |
Enables XSS filtering (blocks entire page) |
2. X-Frame-Options
Controls whether your site can be embedded in frames or iframes. This prevents clickjacking attacks where malicious sites trick users into clicking on concealed elements.
| Value | Description |
|---|---|
DENY |
Site cannot be displayed in frames |
SAMEORIGIN |
Site can only be framed by same origin |
ALLOW-FROM uri |
Site can be framed by specified origin (deprecated) |
3. X-Content-Type-Options
Prevents browsers from MIME-sniffing a response away from the declared content-type. This stops browsers from trying to interpret files as a different MIME type than what's specified.
X-Content-Type-Options: nosniffThe only valid value is nosniff, which forces browsers to respect the declared Content-Type.
4. Referrer-Policy
Controls how much referrer information is sent when navigating from your site to other sites. This is crucial for privacy and may affect analytics or affiliate tracking.
| Policy | Description |
|---|---|
no-referrer |
Never send referrer information |
no-referrer-when-downgrade |
Send referrer unless downgrading from HTTPS to HTTP |
origin |
Send only the origin (no path/query) |
origin-when-cross-origin |
Send full URL for same-origin, only origin for cross-origin |
strict-origin |
Send origin unless downgrading HTTPS to HTTP |
strict-origin-when-cross-origin |
Default: full URL for same-origin, origin for cross-origin (not on downgrade) |
unsafe-url |
Always send full URL (may leak private information) |
strict-origin-when-cross-origin by default, or unsafe-url if tracking requires it.
5. Permissions-Policy (formerly Feature-Policy)
Controls which browser features and APIs can be used in your site and in embedded iframes. This provides granular control over powerful browser capabilities.
Common Directives:
accelerometer- Control access to device accelerometercamera- Control access to device camerageolocation- Control access to GPS/locationmicrophone- Control access to device microphonepayment- Control Payment Request APIusb- Control WebUSB APIfullscreen- Control fullscreen capability
6. Content-Security-Policy (CSP)
The most powerful security header, CSP helps protect against Cross-Site Scripting (XSS), data injection attacks, and other code injection vulnerabilities. It defines trusted sources for content loading and execution.
Key Benefits:
- Prevents execution of malicious scripts
- Blocks unauthorized resource loading
- Mitigates XSS and data injection attacks
- Provides detailed violation reporting
Implementing Security Headers in NGINX
Navigate to the NGINX includes directory:
cd /etc/nginx/includes/Create the HTTP headers configuration file:
sudo nano http_headers.confAdd the following security headers:
# Referrer Policy - Choose appropriate option
#add_header Referrer-Policy "no-referrer";
add_header Referrer-Policy "strict-origin-when-cross-origin";
#add_header Referrer-Policy "unsafe-url";
# Content Security
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
# Permissions Policy
add_header Permissions-Policy 'accelerometer=(), camera=(), clipboard-read=(), clipboard-write=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), fullscreen=(self "https://www.youtube.com")';Including Headers in Site Configuration
Navigate to sites-available directory:
cd /etc/nginx/sites-available/Edit your site configuration:
sudo nano example.com.confAdd the include directive above your PHP processing block:
include /etc/nginx/includes/http_headers.conf;Verifying Headers
Test NGINX configuration:
sudo nginx -tReload NGINX:
sudo systemctl reload nginxCheck headers with curl:
curl -I https://example.comEnhanced Browser Caching with Security Headers
Create an updated browser caching configuration:
sudo nano /etc/nginx/includes/browser_caching_security_headers.confexpires 30d;
etag on;
if_modified_since exact;
add_header Pragma "public";
add_header Cache-Control "public, no-transform";
try_files $uri $uri/ /index.php?$args;
include /etc/nginx/includes/http_headers.conf;
access_log off;This configuration:
- Sets 30-day cache expiration
- Enables ETags for cache validation
- Uses exact if-modified-since checking
- Includes security headers for cached resources
- Disables access logging for static assets
๐ WordPress-Specific Security Configuration
WordPress requires specific security measures to prevent unauthorized access and code execution. NGINX provides powerful tools to harden WordPress installations against common attacks.
Creating the Security Directives File
Navigate to includes directory:
cd /etc/nginx/includes/Create the security directives file:
sudo nano nginx_security_directives.confAdd comprehensive WordPress security rules:
# WORDPRESS-SAFE NGINX FIREWALL RULESET
# Based on NGINX 8G Firewall - Updated December 2026
# Low false-positive risk
# Disable favicon logging
location = /favicon.ico {
access_log off;
log_not_found off;
}
# Deny access to sensitive core and config files
location = /wp-config.php { deny all; }
location = /wp-admin/install.php { deny all; }
location ~* ^/(readme|license|licence)\.(txt|html)$ { deny all; }
location ~* \.ini$ { deny all; }
# Harden WordPress core
location ~* ^/wp-includes/[^/]+\.php$ { deny all; }
location ~* ^/wp-includes/js/tinymce/langs/.+\.php$ { deny all; }
location ~* ^/wp-includes/theme-compat/ { deny all; }
# Prevent PHP execution in uploads, themes, and plugins
location ~* ^/wp-content/uploads/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; }
location ~* ^/wp-content/plugins/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; }
location ~* ^/wp-content/themes/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; }
# Protect upgrade and backup directories
location ~* ^/wp-content/(upgrade|backup-.*)/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; }
# Block development and dependency files
location ~* (composer\.(json|lock)|package\.json|yarn\.lock|/vendor/|/node_modules/) { deny all; }
# Block dangerous HTTP methods
if ($request_method ~* ^(TRACE|DELETE|TRACK)$) {
return 403;
}
# Block vulnerability scanners
if ($http_user_agent ~* (nikto|sqlmap|masscan|nmap|dirbuster|acunetix|openvas)) {
return 444;
}WordPress Security Layer Architecture
Block Scanner Bots
& Bad Methods
Deny Sensitive
Config Files
Block PHP Execution
in Uploads
Rate Limiting
on wp-login
Including Security Rules in Site Configuration
Edit your site configuration:
cd /etc/nginx/sites-available/sudo nano example.com.confAdd above your PHP processing block:
include /etc/nginx/includes/nginx_security_directives.conf;Allowing Specific Plugin PHP Execution
Some plugins require PHP execution. By default, we block all PHP in the plugins directory. To allow specific files:
Testing PHP Execution Block
Create a test PHP file:
cd /var/www/example.com/public_html/wp-content/plugins/sudo nano test556.phpAdd this content:
<?php
phpinfo();
?>Access via browser: https://example.com/wp-content/plugins/test556.php
Result: NGINX returns 403 Forbidden (PHP execution blocked)
Allowing Specific Plugin Files
If a plugin needs PHP execution, add a specific location block after your main PHP processing block:
location = /wp-content/plugins/test556.php {
allow all;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}Test and reload:
sudo nginx -tsudo systemctl reload nginx && sudo systemctl restart php8.3-fpmCleanup
Remove the test file:
sudo rm /var/www/example.com/public_html/wp-content/plugins/test556.phpDisabling WordPress File Modifications
Add this to wp-config.php to prevent plugin/theme installation via admin panel:
define('DISALLOW_FILE_MODS', true);After adding, reload PHP-FPM:
sudo systemctl reload php8.3-fpmDISALLOW_FILE_MODS enabled, updates must be done via command line or SFTP. This prevents attackers from installing malicious plugins even if they compromise the admin account.
๐ File Ownership and Permissions
Proper file permissions are critical for WordPress security. Too permissive settings allow unauthorized modifications, while too restrictive settings break functionality. We'll cover both standard and hardened configurations.
Permission Strategy
| Permission | Directories | Files | Access Level |
|---|---|---|---|
| 770 | โ Standard writable | โ | Owner + Group: Full |
| 660 | โ | โ Standard writable | Owner + Group: Read/Write |
| 550 | โ Hardened read-only | โ | Owner + Group: Read/Execute |
| 440 | โ | โ Hardened read-only | Owner + Group: Read only |
| 400 | โ | โ Sensitive config | Owner: Read only |
Standard Permission Configuration
Standard permissions allow WordPress to update itself and manage uploads while maintaining reasonable security.
Navigate to your site directory:
cd /var/www/example.com/Set ownership to your site user:
sudo chown -R username:username public_html/Set directory permissions (770 = rwxrwx---):
sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {} \;Set file permissions (660 = rw-rw----):
sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {} \;Secure wp-config.php (400 = r--------):
sudo chmod 400 public_html/wp-config.phpHardened Permission Configuration
Hardened permissions prevent WordPress from modifying core files while allowing uploads and content management. This significantly improves security but requires manual updates.
Set ownership:
sudo chown -R username:username public_html/Set read-only directory permissions (550 = r-xr-x---):
sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {} \;Set read-only file permissions (440 = r--r-----):
sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {} \;Set writable permissions for wp-content (uploads, cache, etc.):
sudo find /var/www/example.com/public_html/wp-content/ -type d -exec chmod 770 {} \;sudo find /var/www/example.com/public_html/wp-content/ -type f -exec chmod 660 {} \;Permission Breakdown
| Octal | Symbolic | Description | Use Case |
|---|---|---|---|
| 770 | rwxrwx--- | Owner & group: full control | Writable directories (standard) |
| 660 | rw-rw---- | Owner & group: read/write | Writable files (standard) |
| 550 | r-xr-x--- | Owner & group: read/execute | Read-only directories (hardened) |
| 440 | r--r----- | Owner & group: read only | Read-only files (hardened) |
| 400 | r-------- | Owner only: read | Sensitive config files |
Verifying Permissions
Check current permissions:
ls -l /var/www/example.com/public_html/Check specific file permissions:
ls -l /var/www/example.com/public_html/wp-config.phpWhen to Use Each Configuration
- Standard Permissions:
- Development environments
- Sites requiring automatic updates
- Sites managed by non-technical users
- Trusted single-user environments
- Hardened Permissions:
- Production servers
- High-value or high-traffic sites
- Multi-tenant hosting environments
- Sites handling sensitive data
- When paired with professional security monitoring
โฑ๏ธ Rate Limiting Configuration
Rate limiting protects your WordPress site from brute force attacks, particularly on wp-login.php and xmlrpc.php. By restricting the number of requests from a single IP address, you can significantly reduce the effectiveness of automated attacks.
Rate Limiting Protection Flow
5-10 requests/min
โ Allowed
30 requests/min
burst: 20
100+ requests/min
โ Blocked (444)
Understanding Rate Limit Zones
Rate limit zones define how requests are tracked and limited. They're defined globally in nginx.conf and applied to specific locations.
| Parameter | Description | Example Value |
|---|---|---|
| zone name | Identifier for the rate limit zone | wp |
| zone size | Memory allocated for tracking IPs | 10m (stores ~160,000 IPs) |
| rate | Allowed requests per time period | 30r/m (30 requests per minute) |
| burst | Excess requests allowed in short burst | 20 |
| nodelay | Process burst requests immediately | enabled |
Configuring Global Rate Limit Zone
Edit the main NGINX configuration:
cd /etc/nginx/sudo nano nginx.confAdd the rate limit zone in the http block:
##
# Rate Limiting
limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;$binary_remote_addr- Uses binary IP format (saves memory)zone=wp:10m- Creates a 10MB shared memory zone named "wp"rate=30r/m- Allows 30 requests per minute per IP
Creating Site-Specific Rate Limiting
Create a rate limiting configuration file:
cd /etc/nginx/includes/sudo nano rate_limiting_example.com.confAdd rate limiting for WordPress login and XML-RPC:
location = /wp-login.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
location = /xmlrpc.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}php8.3-fpm-example.com.sock with your actual PHP-FPM socket path. Each site should have its own socket.
Including Rate Limiting in Site Configuration
Edit your site's NGINX configuration:
cd /etc/nginx/sites-available/sudo nano example.com.confInclude the rate limiting configuration:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting_example.com.conf;Testing and Applying Configuration
Test NGINX configuration:
sudo nginx -tReload NGINX:
sudo systemctl reload nginxUnderstanding Rate Limit Behavior
Example Scenario:
- Zone configured: 30 requests/minute with burst=20
- Average rate: 1 request every 2 seconds (30/60 = 0.5 req/sec)
- Burst allows up to 20 additional requests immediately
- After burst is exhausted, requests are limited to 0.5/sec
- Exceeded requests return status code 444 (connection closed)
Monitoring Rate Limiting
Check NGINX error logs for rate limit violations:
sudo tail -f /var/log/nginx/error.log | grep limitingTypical rate limit log entry:
2026/01/29 10:30:15 [error] limiting requests, excess: 20.500 by zone "wp", client: 192.168.1.100, server: example.com, request: "POST /wp-login.php HTTP/2.0"Adjusting Rate Limits
If legitimate users are being blocked, you can adjust the limits:
| Use Case | Recommended Rate | Burst |
|---|---|---|
| Very strict (high security) | 10r/m | 5 |
| Strict (recommended) | 30r/m | 20 |
| Moderate | 60r/m | 30 |
| Lenient (multiple users) | 120r/m | 50 |
Whitelisting Trusted IPs
To exempt specific IPs from rate limiting:
geo $limit {
default 1;
192.168.1.100 0; # Your office IP
10.0.0.0/8 0; # Internal network
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=wp:10m rate=30r/m;Additional Rate Limiting Targets
Consider applying rate limiting to these endpoints as well:
/wp-cron.php- Prevent cron exhaustion/wp-json/- Protect REST API endpoints/wp-comments-post.php- Prevent comment spam- Custom admin AJAX endpoints
๐พ Database Security and Privileges
WordPress database security is often overlooked but crucial. By limiting database privileges, you can reduce the potential damage from SQL injection attacks and compromised WordPress installations.
Principle of Least Privilege
WordPress typically doesn't need full database privileges. Most operations only require SELECT, INSERT, UPDATE, and DELETE. Administrative operations like CREATE TABLE and ALTER TABLE are only needed during plugin installations or updates.
Revoking All Privileges
First, remove all existing privileges from your WordPress database user:
REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'hostname';Granting Minimal Required Privileges
Grant only the essential privileges for normal WordPress operation:
GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'hostname';Granting Additional Privileges When Needed
When installing plugins or updating WordPress, temporarily grant additional privileges:
GRANT CREATE, ALTER, INDEX ON site_db.* TO 'site_user'@'hostname';After completing the installation or update, revoke these privileges:
REVOKE CREATE, ALTER, INDEX ON site_db.* FROM 'site_user'@'hostname';Applying Changes
After modifying privileges, flush the privilege tables:
FLUSH PRIVILEGES;Database Privilege Reference
| Privilege | Required For | Always Needed? |
|---|---|---|
| SELECT | Reading data | โ Yes |
| INSERT | Creating new records | โ Yes |
| UPDATE | Modifying existing records | โ Yes |
| DELETE | Removing records | โ Yes |
| CREATE | Creating new tables | โ Only during setup/plugin install |
| ALTER | Modifying table structure | โ Only during updates |
| INDEX | Managing indexes | โ Only during optimization |
| DROP | Deleting tables | โ Not recommended |
Automation Script for Privilege Management
Create a script to easily toggle privileges during maintenance:
#!/bin/bash
# wp-db-privileges.sh
# Usage: ./wp-db-privileges.sh [enable|disable]
DB_NAME="site_db"
DB_USER="site_user"
DB_HOST="localhost"
if [ "$1" == "enable" ]; then
mysql -e "GRANT CREATE, ALTER, INDEX ON ${DB_NAME}.* TO '${DB_USER}'@'${DB_HOST}'; FLUSH PRIVILEGES;"
echo "Additional privileges granted. Remember to disable after updates!"
elif [ "$1" == "disable" ]; then
mysql -e "REVOKE CREATE, ALTER, INDEX ON ${DB_NAME}.* FROM '${DB_USER}'@'${DB_HOST}'; FLUSH PRIVILEGES;"
echo "Additional privileges revoked. Normal operations only."
else
echo "Usage: $0 [enable|disable]"
fi๐งช Testing and Verification
After implementing all security configurations, thorough testing ensures everything works correctly without breaking functionality.
NGINX Configuration Testing
sudo nginx -tThis command checks for syntax errors. Always run before reloading NGINX.
Testing HTTP Headers
Verify all security headers are present:
curl -I https://example.comTest specific static asset headers:
curl -I https://example.com/wp-content/themes/your-theme/style.cssExternal Security Testing Tools
- SSL Labs: SSL Server Test - Comprehensive SSL/TLS analysis (aim for A+ rating)
- Security Headers: SecurityHeaders.com - Analyzes HTTP security headers (aim for A+ rating)
- HTTP/3 Test: HTTP3Check.net - Verify HTTP/3 and QUIC support
- Mozilla Observatory: Observatory - Overall security scan and recommendations
Functional Testing Checklist
- โ WordPress admin panel accessible
- โ Posts and pages display correctly
- โ Media uploads work (images, videos, documents)
- โ Plugin installation/updates (with appropriate privileges)
- โ Theme customization functions
- โ Contact forms submit properly
- โ Comment system works (if enabled)
- โ Search functionality operational
- โ RSS feeds accessible
- โ AJAX requests complete successfully
Performance Verification
Check server response times:
curl -w "@curl-format.txt" -o /dev/null -s https://example.comCreate curl-format.txt:
time_namelookup: %{time_namelookup}s\n
time_connect: %{time_connect}s\n
time_appconnect: %{time_appconnect}s\n
time_pretransfer: %{time_pretransfer}s\n
time_redirect: %{time_redirect}s\n
time_starttransfer: %{time_starttransfer}s\n
time_total: %{time_total}s\n๐ Troubleshooting Common Issues
NGINX Configuration Errors
Issue: NGINX fails to reload after configuration change
Solution: Check error logs for specific issues:
sudo nginx -t
sudo tail -f /var/log/nginx/error.logFile Permission Issues
Issue: WordPress cannot upload files or update
Solution: Verify ownership and permissions:
ls -la /var/www/example.com/public_html/wp-content/uploads/
sudo chown -R username:www-data /var/www/example.com/public_html/wp-content/
sudo chmod 775 /var/www/example.com/public_html/wp-content/uploads/PHP-FPM Pool Issues
Issue: 502 Bad Gateway errors
Solution: Check PHP-FPM status and logs:
sudo systemctl status php8.3-fpm
sudo tail -f /var/log/php8.3-fpm.log
sudo tail -f /var/log/fpm-php.example.com.logRate Limiting False Positives
Issue: Legitimate users being blocked
Solution: Increase rate limits or add IP whitelist:
# In nginx.conf
limit_req_zone $binary_remote_addr zone=wp:10m rate=60r/m; # Increase from 30r/mSSL Certificate Issues
Issue: Certificate verification failures
Solution: Check certificate validity and renewal:
sudo certbot certificates
sudo certbot renew --dry-run๐ง Ongoing Maintenance
Regular Security Tasks
- Weekly:
- Review NGINX access and error logs
- Check for failed login attempts
- Monitor disk space and resource usage
- Monthly:
- Update WordPress core, themes, and plugins
- Review and update security headers
- Test SSL certificate renewal
- Audit user accounts and permissions
- Quarterly:
- Full security scan with external tools
- Review and update firewall rules
- Optimize database and check backups
- Update documentation
Log Rotation
Ensure logs don't fill disk space. Configure log rotation:
sudo nano /etc/logrotate.d/nginx/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}Backup Strategy
Essential files and directories to backup:
/etc/nginx/- All NGINX configurations/var/www/example.com/- Website files/etc/php/8.3/fpm/pool.d/- PHP-FPM pool configs- Database dumps (using mysqldump)
/etc/letsencrypt/- SSL certificates
๐ฏ Conclusion
This comprehensive guide has covered advanced NGINX configuration for WordPress hosting with a focus on security, performance, and maintainability. By implementing these configurations, you've created a robust, hardened environment that protects against common web attacks while maintaining excellent performance.
Key Achievements
A+ Rating
HTTP/3 & QUIC
Multiple Layers
CSP Protection
Per-Site Pools
Resource Limits
Brute Force Protection
DDoS Mitigation
Security Layers Implemented
- Network Layer: SSL/TLS encryption, HTTP/3, secure ciphers
- Application Layer: Security headers, CSP, Referrer Policy
- WordPress Layer: File blocking, upload restrictions, disabled functions
- Rate Limiting: Login protection, XML-RPC restrictions
- File System: Hardened permissions, restricted access
- Database Layer: Minimal privileges, access control
Next Steps
- Monitor logs regularly for suspicious activity
- Keep all software updated (NGINX, PHP, WordPress)
- Implement automated backups
- Consider additional security tools (fail2ban, ModSecurity)
- Set up monitoring and alerting (Prometheus, Grafana)
- Document your specific configuration customizations
Remember: Security is about layers. No single measure provides complete protection, but together, these configurations create a defense-in-depth strategy that makes your WordPress installation significantly more resistant to attacks.