NGINX Server Configuration Guide - Section 15
About DDoS Protection: While NGINX provides various features to help protect against DDoS
attacks, including connection limits and request rate limiting, these mechanisms are not specifically
tailored for WordPress sites. For comprehensive protection, it's recommended to use a Web Application
Firewall (WAF) plugin or services like Cloudflare.
1. NGINX Logs Management
Log files are essential for monitoring server activity, debugging issues, and identifying security threats.
NGINX stores its logs in the /var/log/nginx directory by default.
Viewing Log Files
cd /var/log/nginx
ls
sudo cat log_file_name.log
sudo less log_file_name.log
2. PHP-FPM Pool Configuration
PHP-FPM (FastCGI Process Manager) pools allow you to run multiple PHP applications with different
configurations and user permissions. This provides better security isolation between different sites.
PHP-FPM Pool Setup Workflow
Step 1: Create System User
sudo useradd username
Step 2: Configure User Groups
sudo usermod -a -G username www-data
Step 3: Copy and Edit Pool Configuration
sudo cp www.conf example.com.conf
Step 4: Create Log Files with Proper Permissions
Step 5: Reload PHP-FPM Service
sudo systemctl reload php8.3-fpm
Creating a System User
Adding User to Groups
sudo usermod -a -G username_group www-data
sudo usermod -a -G www-data username_group
sudo usermod -a -G username_group $USER
Creating PHP-FPM Pool Configuration
cd /etc/php/8.3/pool.d/
ls
sudo cp www.conf example.com.conf
ls
sudo nano example.com.conf
Essential Pool Configuration Settings
[example]
user = username
group = username
listen = /run/php/php8.3-fpm-example.com.sock
rlimit_files = 15000
rlimit_core = 100
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
Creating FPM Log File
sudo touch /var/log/fpm-php.example.com.log
sudo chown user:www-data /var/log/fpm-php.example.com.log
sudo chmod 660 /var/log/fpm-php.example.com.log
Reloading PHP-FPM Service
sudo systemctl reload php8.3-fpm
Verifying Socket Configuration
sudo grep "listen = /" example.conf
Updating NGINX Configuration
sudo nano /etc/nginx/sites-available/example.com.conf
Add the following directive in your PHP processing block:
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
Testing and Reloading NGINX
sudo nginx -t
sudo systemctl reload nginx
Setting File Permissions
sudo chown -R username:username public_html/
sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {} \;
Creating Test PHP File
cd public_html/
sudo nano filename.php
<?php
phpinfo();
?>
Advanced PHP Configuration
You can configure additional PHP settings within the pool configuration file:
sudo nano /etc/php/8.3/pool.d/example.com.conf
Enabling URL File Opening
php_admin_flag[allow_url_fopen] = on
Disabling Dangerous Functions
; ENABLED FUNCTIONS
; disk_free_space
; DISABLED 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, system
sudo systemctl reload php8.3-fpm
Hardened File Permissions
For enhanced security, you can apply more restrictive permissions:
sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {} \;
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 {}
\;
Configuring Temporary Directories
cd /etc/php/8.3/fpm/pool.d/
sudo nano example.com.conf
php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/
php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/
Creating and Securing Temporary Directory
cd /var/www/example.com/
ls -l
sudo mkdir tmp/
sudo chown username:username tmp/
sudo chmod 770 tmp/
ls -l
Configuring Open Basedir
The open_basedir directive restricts PHP file operations to specified directories, enhancing
security:
cd /etc/php/8.3/fpm/pool.d/
sudo nano example.com.conf
php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/
sudo systemctl reload php8.3-fpm
3. SSL Certificate Setup with Let's Encrypt
Securing your website with SSL/TLS certificates is essential for protecting user data and improving SEO
rankings. Let's Encrypt provides free SSL certificates.
Installing Certbot
sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflare
Obtaining SSL Certificate
sudo certbot certonly --webroot -w /var/www/example.com/public_html/ -d
example.com -d www.example.com
Creating DH Parameters
Diffie-Hellman parameters improve SSL/TLS security:
cd /etc/nginx/
sudo mkdir ssl/
cd ssl/
sudo openssl dhparam -out dhparam.pem 2048
ls
Creating SSL Certificate Configuration
sudo nano /etc/nginx/ssl/ssl_certs_example.com.conf
ssl_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;
Creating Global SSL Configuration
sudo nano /etc/nginx/ssl/ssl_all_sites.conf
# CONFIGURATION RESULTS IN A+ RATING AT SSLLABS.COM
# WILL UPDATE DIRECTIVES TO MAINTAIN A+ RATING - CHECK DATE
# DATE: 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 must be on a single line, do not split over multiple lines
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;
# LETS ENCRYPT REMOVED SUPPORT 7 MAY 2025 FOR SSL STAPLING
# REMOVE OR COMMENT BOTH ssl_stapling directives
#ssl_stapling on;
#ssl_stapling_verify on;
# resolver set to Cloudflare
# timeout can be set up to 30s
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;
# After setting up ALL of your sub domains - comment the above and uncomment the directive below
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;" always;
# Enable QUIC and HTTP/3
ssl_early_data on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
add_header x-quic 'H3';
quic_retry on;
Note: Let's Encrypt removed support for SSL stapling on May 7, 2025. The ssl_stapling
directives are commented out in the configuration above.
4. HTTP/3 and QUIC Configuration
HTTP/3 is the latest version of the HTTP protocol, using QUIC as the transport protocol. It provides improved
performance and security.
Secure Server Block Configuration
server {
listen 80;
server_name example.com www.example.com;
# ADD REDIRECT TO HTTPS: 301 PERMANENT 302 TEMPORARY
return 301 https://example.com$request_uri;
}
Primary Server with HTTP/3 Support
# Primary server with reuseport
listen 443 ssl;
http2 on;
listen 443 quic reuseport;
http3 on;
Additional Servers (Without Reuseport)
# Secondary servers without reuseport
listen 443 ssl;
http2 on;
listen 443 quic;
http3 on;
Including SSL Configuration Files
include /etc/nginx/ssl/ssl_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
sudo nginx -t
sudo systemctl reload nginx
Testing HTTP/3
To test HTTP/3 functionality, temporarily add this directive to prevent browser caching:
# PREVENTS BROWSER CACHING SITE - USED TO TEST HTTP3
# COMMENT OR REMOVE DIRECTIVE AFTER CONFIRMING HTTP3 IS ENABLED
add_header Cache-Control 'no-cache,no-store';
Adding HTTP_HOST Parameter
Add the following in your PHP processing block:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Testing SSL and HTTP Configuration
curl -I http://example.com
curl -I http://www.example.com
curl -I https://www.example.com
curl -I https://example.com
SSL Testing: Test your certificate at
ssllabs.com – you should receive an A+ rating.
Browser Console Testing: Use Browser Console → Network → Refresh Page → Right-click column
headers → Ensure "Protocol" is selected → h3 is displayed, indicating content being served over HTTP/3
Certbot Management Commands
sudo certbot certificates
sudo certbot delete
sudo certbot renew
sudo certbot renew --force-renewal
SSL Certificate Auto-Renewal with Cron
# m h dom mon dow command
00 1 14,28 * * certbot renew --force-renewal
00 2 14,28 * * systemctl reload nginx
This configuration renews certificates on the 14th and 28th of each month at 1:00 AM, and
reloads NGINX at 2:00 AM.
Security headers protect your website from various attacks including XSS, clickjacking, and MIME sniffing.
Creating HTTP Headers Configuration
cd /etc/nginx/includes/
sudo nano http_headers.conf
# -------------------------------------------------------
# Add Header Referrer-Policy - Uncomment desired directive
# -------------------------------------------------------
#add_header Referrer-Policy "no-referrer";
add_header Referrer-Policy "strict-origin-when-cross-origin";
#add_header Referrer-Policy "unsafe-url";
# ------------------------------------------------------
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
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
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Add the following directive above the PHP processing location block:
include /etc/nginx/includes/http_headers.conf;
sudo nginx -t
sudo systemctl reload nginx
Browser Caching with Security Headers
sudo nano /etc/nginx/includes/browser_caching_security_headers.conf
expires 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;
sudo nginx -t
sudo systemctl reload nginx
6. File Ownership & Permissions
Proper file permissions are crucial for security. There are two permission schemes: standard and hardened.
| Permission Type |
Directories |
Files |
Use Case |
| Standard |
770 |
660 |
Development, frequent file changes |
| Hardened |
550 |
440 |
Production, enhanced security |
| wp-content (Hardened) |
770 |
660 |
Uploads and theme/plugin management |
Standard Permissions
cd /var/www/example.com/
sudo chown -R username:username public_html/
sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {} \;
sudo chmod 400 public_html/wp-config.php
Hardened Permissions
cd /var/www/example.com/
sudo chown -R username:username public_html/
sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {} \;
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 {}
\;
7. WordPress Security Directives
Implementing security rules at the NGINX level provides an additional layer of protection for WordPress
installations.
Creating WordPress Security Configuration
cd /etc/nginx/includes/
sudo nano nginx_security_directives.conf
##
# WORDPRESS-SAFE NGINX 8G (based) FIREWALL Ruleset
# low false-positive risks
# Updated December 2026
# 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 WP 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 plugin directories
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/dirs
location ~* (composer\.(json|lock)|package\.json|yarn\.lock|/vendor/|/node_modules/) { deny all; }
# Block dangerous or unused HTTP methods
if ($request_method ~* ^(TRACE|DELETE|TRACK)$) { return 403; }
# Block known vulnerability scanners
if ($http_user_agent ~* (nikto|sqlmap|masscan|nmap|dirbuster|acunetix|openvas)) { return 444; }
Including Security Configuration in Site
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Add the following directive above the PHP processing location block:
include /etc/nginx/includes/nginx_security_directives.conf;
sudo nginx -t
sudo systemctl reload nginx
Allowing PHP Execution for Specific Plugins
Some plugins require PHP execution in the plugins directory. Here's how to create a secure exception:
Testing Plugin PHP Blocking
cd /var/www/example.com/
sudo nano public_html/wp-content/plugins/test556.php
<?php
phpinfo();
?>
Browse to: https://example.com/wp-content/plugins/test556.php
NGINX will return a 403 error, confirming PHP execution is blocked.
Creating Exception for Specific Plugin
Add this location block below your PHP processing location 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.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm
Cleanup Test File
cd /var/www/example.com/public_html/wp-content/plugins/
sudo rm test556.php
8. Rate Limiting
Rate limiting protects your server from brute force attacks and excessive requests to sensitive endpoints
like wp-login.php.
Configuring Rate Limit Zone
cd /etc/nginx/
sudo nano nginx.conf
Add the following in the http context:
##
# Rate Limiting
limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;
Creating Rate Limiting Configuration
cd /etc/nginx/includes/
sudo nano rate_limiting_example.com.conf
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-MODIFY.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-MODIFY.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Important: Replace php8.3-fpm-MODIFY.sock with your actual PHP-FPM socket name
(e.g., php8.3-fpm-example.com.sock).
Including Rate Limiting in Site Configuration
Add this include directive in your server block:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting.conf;
sudo nginx -t
sudo systemctl reload nginx
| Parameter |
Description |
Value |
| rate |
Maximum requests per minute |
30r/m |
| burst |
Allowed burst before rate limiting kicks in |
20 |
| nodelay |
Process burst requests immediately |
enabled |
| limit_req_status |
HTTP status code for rate-limited requests |
444 (close connection) |
Disabling File Modifications in WordPress
Add this to your wp-config.php file to prevent file modifications through the WordPress admin:
define('DISALLOW_FILE_MODS', 'true');
sudo systemctl reload php8.3-fpm
9. Database Privileges
Following the principle of least privilege, grant only the necessary database permissions to WordPress users.
Revoking All Privileges
REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'hostname';
Granting Basic WordPress Privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'hostname';
Granting Additional Privileges (When Needed)
For plugin installations or updates, you may need to temporarily grant additional privileges:
GRANT CREATE, ALTER, INDEX ON database_name.* TO 'username'@'localhost';
Applying Changes
| Privilege |
Required For |
Security Level |
| SELECT |
Reading data |
Essential |
| INSERT |
Creating new records |
Essential |
| UPDATE |
Modifying existing records |
Essential |
| DELETE |
Removing records |
Essential |
| CREATE |
Creating new tables (plugins/updates) |
Temporary |
| ALTER |
Modifying table structure |
Temporary |
| INDEX |
Creating indexes |
Temporary |
Security Best Practice: Grant CREATE, ALTER, and INDEX privileges only when installing or
updating plugins/themes. Revoke these privileges afterwards to minimize security risks.
Conclusion
This comprehensive guide covers essential NGINX configuration aspects for hosting WordPress sites securely
and efficiently. By implementing these configurations, you'll achieve:
- Isolated PHP-FPM pools for better security and resource management
- A+ SSL rating with modern TLS protocols and cipher suites
- HTTP/3 and QUIC support for improved performance
- Comprehensive security headers protecting against common web vulnerabilities
- Rate limiting to prevent brute force attacks
- Proper file permissions balancing security and functionality
- Database privilege restrictions following the principle of least privilege
Remember: Always test configuration changes in a staging environment before applying them
to production. Use sudo nginx -t to validate your configuration syntax before reloading NGINX.
DDoS Protection Note: While NGINX provides connection and request rate limiting, these
mechanisms are not sufficient for protecting WordPress sites against sophisticated DDoS attacks. For
comprehensive DDoS protection, consider:
- Using a Web Application Firewall (WAF) plugin
- Enabling Cloudflare or similar CDN/DDoS protection services
- Contacting your hosting provider for infrastructure-level DDoS mitigation
NGINX Configuration Guide - Section 15
Last Updated: January 2026