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:
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:
# 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
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.
Cloudflare Hotlinking Protection
Recommended Approach
- Requests blocked at CDN level
- No server resources consumed
- One-click enable in dashboard
- Works with Cloudflare proxy
- Limited to specific file types
- Blocks Google Images indexing
NGINX Hotlinking Protection
Alternative Approach
- More customization options
- Fine-grained control
- Consumes server resources
- Incompatible with Cloudflare
- May block legitimate requests
- Increases server load
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:
- Log into Cloudflare dashboard
- Select your domain
- Navigate to Scrape Shield
- Enable Hotlink Protection (single click)
- 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:
- Open Developer Console (F12)
- Navigate to Network tab
- Refresh the page
- Right-click on column headers
- Enable "Protocol" column
- 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.