NGINX Server Configuration Guide

Section 15: Comprehensive Server Security & Optimization

1. Log Management

Proper log management is essential for monitoring server activity, troubleshooting issues, and maintaining security. NGINX logs provide valuable insights into server requests, errors, and performance metrics.

Viewing NGINX Logs

Navigate to the NGINX log directory and view log files:

cd /var/log/nginx
ls
sudo cat log_file_name.log
sudo less log_file_name.log
Pro Tip: Use less for viewing large log files as it allows you to scroll through the content without loading the entire file into memory.

2. PHP-FPM Pool Configuration

PHP-FPM (FastCGI Process Manager) pools allow you to run multiple PHP processes with different configurations, users, and resource limits. This is essential for multi-site hosting and security isolation.

User Management

Create a dedicated user for your website:

sudo useradd username

Group Configuration

Add users to appropriate groups for proper permissions:

sudo usermod -a -G username_group www-data
sudo usermod -a -G www-data username_group
sudo usermod -a -G username_group $USER

Creating a Custom PHP-FPM Pool

cd /etc/php/8.3/fpm/pool.d/
ls
sudo cp www.conf example.com.conf
sudo nano example.com.conf

Pool Configuration Example

[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

PHP-FPM Pool Architecture

NGINX Request
Unix Socket
PHP-FPM Pool (Dedicated User)
PHP Processing
Response to Client

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

Reload PHP-FPM

sudo systemctl reload php8.3-fpm

Update NGINX Configuration

sudo nano /etc/nginx/sites-available/example.com.conf
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;

Test and Reload NGINX

sudo nginx -t
sudo systemctl reload nginx

PHP Security Configuration

Disable dangerous PHP functions for enhanced security:

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, proc_open, proc_terminate, show_source, system

Configure Upload and Temp Directories

cd /var/www/example.com/
sudo mkdir tmp/
sudo chown username:username tmp/
sudo chmod 770 tmp/
php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/ php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/ php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/

3. SSL Certificate Setup with Let's Encrypt

SSL certificates encrypt data transmission between your server and clients, ensuring secure communication. Let's Encrypt provides free SSL certificates with automatic renewal.

Install Certbot

sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflare

Obtain SSL Certificate

sudo certbot certonly --webroot -w /var/www/example.com/public_html/ -d example.com -d www.example.com

Generate Diffie-Hellman Parameters

cd /etc/nginx/
sudo mkdir ssl/
cd ssl/
sudo openssl dhparam -out dhparam.pem 2048

SSL Certificate Configuration

Create a configuration file for SSL certificates:

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;

Universal SSL Configuration (A+ Rating)

sudo nano /etc/nginx/ssl/ssl_all_sites.conf
# SSL CONFIGURATION - A+ RATING AT SSLLABS.COM # UPDATED: MAY 2025 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; 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; # 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;
Achievement: This configuration will give you an A+ rating on SSL Labs!

NGINX Server Block Configuration

# HTTP to HTTPS Redirect server { listen 80; server_name example.com www.example.com; return 301 https://example.com$request_uri; } # HTTPS Server Block (Primary) server { listen 443 ssl; http2 on; listen 443 quic reuseport; http3 on; server_name example.com www.example.com; include /etc/nginx/ssl/ssl_example.com.conf; include /etc/nginx/ssl/ssl_all_sites.conf; }
Important: Use reuseport only on the primary server block. Additional server blocks should use listen 443 quic; without the reuseport directive.

Testing HTTP/3

curl -I https://example.com

Test your HTTP/3 configuration at: https://http3check.net/

Certificate Management Commands

Command Description
sudo certbot certificates List all certificates
sudo certbot delete Delete a certificate
sudo certbot renew Renew certificates
sudo certbot renew --force-renewal Force certificate renewal

Automatic Certificate Renewal

Set up automatic certificate renewal using cron:

sudo crontab -e
# Renew certificates on the 14th and 28th of each month at 1 AM 00 1 14,28 * * certbot renew --force-renewal # Reload NGINX at 2 AM on the same days 00 2 14,28 * * systemctl reload nginx

4. HTTP Security Headers

HTTP security headers protect your website from common vulnerabilities and attacks by instructing browsers how to handle your content securely.

Create Security Headers Configuration

cd /etc/nginx/includes/
sudo nano http_headers.conf
# Referrer Policy add_header Referrer-Policy "strict-origin-when-cross-origin"; # Security Headers 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")';
Header Purpose
Referrer-Policy Controls how much referrer information is shared
X-Content-Type-Options Prevents MIME type sniffing
X-Frame-Options Prevents clickjacking attacks
X-XSS-Protection Enables browser XSS filtering
Permissions-Policy Controls browser feature access

Browser Caching Configuration

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;

5. File Permissions & Ownership

Proper file permissions are crucial for server security. They prevent unauthorized access while allowing necessary operations.

Permission Structure

Type Read Write Execute Numeric
Directories 770
Files 660
Config Files 400

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 (Enhanced Security)

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 {} \;
Security Note: Hardened permissions provide read-only access to most files while allowing write access only where necessary (e.g., wp-content for uploads and plugin operations).

6. WordPress Security Directives

Implement comprehensive security measures to protect your WordPress installation from common attacks and unauthorized access.

Create Security Configuration

cd /etc/nginx/includes/
sudo nano nginx_security_directives.conf
# WORDPRESS-SAFE NGINX FIREWALL RULESET # Updated December 2026 # Disable favicon logging location = /favicon.ico { access_log off; log_not_found off; } # Deny access to sensitive 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 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; }

Allow PHP Execution for Specific Plugins

If a plugin requires PHP execution in the plugins directory:

location = /wp-content/plugins/specific-plugin/file.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; }
Security Warning: Only create exceptions for plugins that absolutely require PHP execution. Always verify the plugin's legitimacy before allowing execution.

7. Rate Limiting

Rate limiting protects your server from brute-force attacks, DDoS attempts, and excessive resource consumption by limiting request frequency from individual IP addresses.

Rate Limiting Flow

Client Request
Rate Limit Check
Within Limit ✓
Process Request
Exceeded Limit ✗
Block (444)

Configure Rate Limiting Zone

sudo nano /etc/nginx/nginx.conf
# Add in http context limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;

Apply Rate Limiting to WordPress Login

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-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; }
Parameter Description
zone=wp Name of the rate limit zone
rate=30r/m 30 requests per minute allowed
burst=20 Allow burst of 20 requests
nodelay Don't delay excess requests
limit_req_status 444 Return 444 (connection closed) status

8. Database Privileges Management

Limiting database privileges reduces the attack surface by ensuring database users have only the permissions they absolutely need.

Revoke All Privileges

REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'hostname';

Grant Minimal Required Privileges

GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'hostname'; FLUSH PRIVILEGES;

Grant Additional Privileges (When Needed)

GRANT CREATE, ALTER, INDEX ON database_name.* TO 'username'@'localhost'; FLUSH PRIVILEGES;
Privilege Required For
SELECT Reading data
INSERT Adding new records
UPDATE Modifying existing records
DELETE Removing records
CREATE Creating tables (plugin installation)
ALTER Modifying table structure
INDEX Creating indexes
Best Practice: Grant CREATE, ALTER, and INDEX privileges only when installing or updating plugins/themes, then revoke them afterwards.

9. Hotlinking Protection

Hotlinking occurs when other websites directly link to your media files (images, videos), consuming your bandwidth and increasing hosting costs without attribution.

Why Hotlinking Is Harmful

Impact of Hotlinking:
  • Steals your bandwidth allocation
  • Increases monthly hosting costs
  • Provides no traffic or attribution to your site
  • Can slow down your website for legitimate users
  • Content theft without permission

Cloudflare Hotlinking Protection Setup

Step-by-Step Process:
  1. Log into Cloudflare dashboard
  2. Select your domain
  3. Navigate to Scrape Shield
  4. Enable Hotlink Protection (single click)
  5. Protection is active immediately at CDN level

How Cloudflare Hotlinking Protection Works

Request Flow with Cloudflare Protection

External Site Hotlinks Image
Request Hits Cloudflare CDN
Cloudflare Checks Referrer
Valid Referrer ✓
Serve Image
Invalid Referrer ✗
Block at CDN

Your server never processes blocked requests!

Limitations to Consider

Cloudflare Hotlinking Protection Limitations:
  • File Types: Only protects specific file types (images, videos)
  • Search Engines: Images will be blocked from Google Images and other image search engines
  • RSS Readers: May block images in RSS feed readers
  • Social Media: Some social media platforms may have issues displaying images

NGINX Hotlinking Protection (Not Recommended)

Why NGINX Hotlinking Protection Is Not Recommended:
  • Server Resources: Every hotlink attempt still hits your server and consumes resources
  • Cloudflare Conflict: Cannot be used if Cloudflare proxy is enabled
  • Performance Impact: Processing hotlink checks increases server load
  • False Positives: May accidentally block legitimate requests
  • Complex Configuration: Requires careful testing to avoid breaking functionality

Recommendation

Best Practice: Use Cloudflare Hotlinking Protection instead of NGINX-level protection. It's more efficient, requires no server resources, and is easier to configure. Only consider NGINX-level protection if you have specific requirements that Cloudflare cannot meet and you're not using Cloudflare's proxy service.

Additional WordPress Security Measures

# Add to wp-config.php to disable file modifications define('DISALLOW_FILE_MODS', 'true');

This prevents plugins and themes from being installed, updated, or deleted through the WordPress admin panel, adding an extra layer of security.

Testing Your Configuration

SSL/TLS Testing

Test your SSL configuration at: https://www.ssllabs.com/ssltest/

Expected result: A+ Rating

HTTP/3 Testing

Verify HTTP/3 is working at: https://http3check.net/

Browser Testing

Using Browser DevTools:
  1. Open Developer Console (F12)
  2. Navigate to Network tab
  3. Refresh the page
  4. Right-click on column headers
  5. Enable "Protocol" column
  6. Look for "h3" indicating HTTP/3

Command Line Testing

curl -I http://example.com
curl -I https://example.com
curl -I https://www.example.com

Final Security Checklist

Security Measure Status
SSL/TLS Certificate Installed
HTTP to HTTPS Redirect
Security Headers Configured
File Permissions Hardened
PHP-FPM Isolated Pools
Rate Limiting Enabled
WordPress Security Rules
Database Privileges Limited
Hotlinking Protection Active
Automatic Certificate Renewal
Congratulations! Your NGINX server is now configured with enterprise-grade security and optimization. Regular monitoring and updates are essential to maintain this security posture.