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.

Table of Contents

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

sudo useradd username

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.
HTTP/3 Testing: Verify HTTP/3 functionality at http3check.net
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

sudo crontab -e
# 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.

5. HTTP Security Headers

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

FLUSH PRIVILEGES;
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:

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:

NGINX Configuration Guide - Section 15

Last Updated: January 2026