Table of Contents
1. Log Management
Proper log management is essential for monitoring server health, troubleshooting issues, and maintaining security. NGINX logs provide valuable insights into server activity and potential security threats.
Viewing NGINX Logs
Navigate to the NGINX log directory and view log files:
Log File Tips
cat - Displays entire log file content (suitable for small files)
less - Allows scrolling through large log files with search capabilities
2. PHP-FPM Pool Configuration
PHP-FPM pools allow you to run multiple websites with different users and configurations, enhancing security through isolation. Each site can have its own dedicated pool with customized settings.
User Management
Create system users and configure group memberships:
Creating Pool Configuration
1Navigate to PHP-FPM pool directory:
2Copy default configuration and create site-specific pool:
Pool Configuration Example
[example]
; Unix user/group of the child processes
user = username
group = username
; The address on which to accept FastCGI requests
listen = /run/php/php8.3-fpm-example.com.sock
; Set open file descriptor rlimit
rlimit_files = 15000
; Set max core size rlimit
rlimit_core = 100
; PHP Configuration Flags
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.example.com.log
php_admin_flag[log_errors] = on
Creating PHP-FPM Log File
Configuring NGINX to Use Custom Pool
Verify socket path:
Update NGINX site configuration:
Test and reload NGINX:
Setting File Permissions
Testing PHP Configuration
Create a PHP info file:
phpinfo();
?>
PHP Security Configuration
Enable specific PHP functions:
Disable dangerous 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
Configuring Temporary Directories
Edit pool configuration:
php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/
Create and configure temporary directory:
Open_basedir Restriction
Restrict PHP file access to specific directories:
PHP-FPM Pool Architecture
3. SSL Certificate Configuration
SSL/TLS certificates encrypt data transmission between servers and clients, ensuring secure communications. Let's Encrypt provides free, automated SSL certificates.
Installing Certbot
Obtaining SSL Certificate
Generating DH Parameters
Diffie-Hellman parameters enhance SSL security:
SSL Certificate Configuration File
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
Global SSL Configuration
# 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 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 set to 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;
# 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;
SSL Stapling Note
Let's Encrypt removed support for SSL stapling on May 7, 2025. The ssl_stapling directives should be commented out or removed.
Secure Server Block Configuration
HTTP to HTTPS redirect:
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
HTTPS configuration with HTTP/2 and HTTP/3:
listen 443 ssl;
http2 on;
listen 443 quic reuseport;
http3 on;
# Second server (without reuseport)
listen 443 ssl;
http2 on;
listen 443 quic;
http3 on;
# Include SSL configuration files
include /etc/nginx/ssl/ssl_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
Testing Configuration
Testing HTTP/3 and QUIC
Add to location / context to test (temporary):
# COMMENT OR REMOVE AFTER CONFIRMING HTTP/3 IS ENABLED
add_header Cache-Control 'no-cache,no-store';
Add HTTP_HOST parameter in PHP processing block:
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 with cURL
SSL Testing Resources
SSL Labs: https://www.ssllabs.com/ssltest/ (Should receive A+ rating)
HTTP/3 Check: https://http3check.net/
Browser Console: Network tab → Right-click → Enable "Protocol" column → Look for "h3"
Certbot Management Commands
SSL Certificate Auto-Renewal
Configure cron for automatic certificate renewal:
00 1 14,28 * * certbot renew --force-renewal
# Reload NGINX at 2:00 AM on the same days
00 2 14,28 * * systemctl reload nginx
SSL/TLS Connection Flow
4. HTTP Security Headers
HTTP security headers protect websites from common attacks like XSS, clickjacking, and MIME-type sniffing. Implementing proper security headers is crucial for modern web security.
Creating Security Headers Configuration
# 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")';
| 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 features and APIs |
Enabling Security Headers
Include in NGINX site configuration (place ABOVE PHP processing block):
Browser Caching and Security Headers
Create optimized caching configuration:
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 Ownership and Permissions
Proper file permissions are critical for security, preventing unauthorized access while allowing necessary operations. We implement two permission schemes: standard and hardened.
Standard Permissions
Suitable for development environments or sites requiring frequent updates:
| Permission | Value | Description |
|---|---|---|
| Directories | 770 | Owner and group can read, write, execute |
| Files | 660 | Owner and group can read and write |
| wp-config.php | 400 | Owner can only read |
Hardened Permissions
Recommended for production environments requiring maximum security:
| Location | Type | Permission | Description |
|---|---|---|---|
| Root directories | Directory | 550 | Read and execute only |
| Root files | File | 440 | Read only |
| wp-content directories | Directory | 770 | Full access for uploads/plugins |
| wp-content files | File | 660 | Read and write for content |
Permission Hierarchy
6. WordPress NGINX Security Directives
This comprehensive firewall ruleset protects WordPress installations from common attacks and vulnerabilities with low false-positive risks.
Creating Security Configuration
# 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 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; }
Enabling WordPress Security
Include in NGINX site configuration (place ABOVE PHP processing block):
Allowing Specific Plugin PHP Execution
Some plugins require PHP execution. Here's how to allow specific files:
1Create test PHP file:
phpinfo();
?>
2Test in browser (should return 403):
https://example.com/wp-content/plugins/test556.php
3Create exception for specific plugin:
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;
}
4Test and reload:
5Clean up test file:
Security Best Practice
Only create exceptions for trusted plugins that absolutely require PHP execution. Each exception potentially opens a security vulnerability.
7. Rate Limiting Configuration
Rate limiting protects your server from brute-force attacks, especially on login pages and XML-RPC endpoints. It restricts the number of requests from a single IP address.
Global Rate Limit Configuration
Edit main NGINX configuration:
Add to http block:
# Rate Limiting
limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;
Rate Limit Explanation
zone=wp:10m - Creates 10MB memory zone named "wp"
rate=30r/m - Allows 30 requests per minute per IP address
burst=20 - Allows burst of 20 additional requests
nodelay - Process burst requests immediately
Creating Site-Specific Rate Limit Configuration
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 "MODIFY" with your actual PHP-FPM socket name (e.g., example.com)
Including Rate Limiting in Site Configuration
include /etc/nginx/includes/rate_limiting_example.com.conf;
Test and reload:
Rate Limiting Flow
Additional WordPress Hardening
Disable file modifications in wp-config.php:
8. Database Privilege Hardening
By default, WordPress database users are granted ALL PRIVILEGES. For enhanced security, we restrict privileges to only what's essential for normal operations.
⚠️ Critical Warnings
- Backup First: Always backup your site and database before making privilege changes
- Test on Development: Test privilege changes on a development server first
- WooCommerce Exception: Do NOT restrict privileges for WooCommerce sites - they require full database access
- Plugin Compatibility: Some plugins may require additional privileges - consult plugin documentation
Understanding Database Privileges
| Privilege | Description | Essential |
|---|---|---|
| SELECT | Read data from tables | ✓ Yes |
| INSERT | Add new records to tables | ✓ Yes |
| UPDATE | Modify existing records | ✓ Yes |
| DELETE | Remove records from tables | ✓ Yes |
| CREATE | Create new tables | Optional |
| ALTER | Modify table structure | Optional |
| INDEX | Create/drop indexes | Optional |
| DROP | Delete tables | No |
Step-by-Step Privilege Restriction Process
1Locate database credentials:
Look for these values:
DB_NAME - Your database name
DB_USER - Your database username
2Log into MariaDB:
3Revoke all existing privileges:
REVOKE ALL PRIVILEGES ON mysite_db.* FROM 'mysite_user'@'localhost';
Important Notes
• Ensure no space between database name and period
• Ensure no space between period and asterisk
• Use single quotes around username
4Grant essential privileges:
GRANT SELECT, INSERT, UPDATE, DELETE ON mysite_db.* TO 'mysite_user'@'localhost';
5Verify new privileges:
Expected Output
You should see grants for SELECT, INSERT, UPDATE, and DELETE only
Granting Additional Privileges (If Needed)
If plugins require additional privileges, grant them selectively:
GRANT CREATE, ALTER, INDEX ON mysite_db.* TO 'mysite_user'@'localhost';
Verify updated privileges:
Finalizing Changes
6Flush privileges and exit:
Complete Example Session
-- Revoke all privileges
REVOKE ALL PRIVILEGES ON mysite_db.* FROM 'mysite_user'@'localhost';
-- Grant essential privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON mysite_db.* TO 'mysite_user'@'localhost';
-- Verify privileges
SHOW GRANTS FOR 'mysite_user'@'localhost';
-- (Optional) Grant additional privileges
GRANT CREATE, ALTER, INDEX ON mysite_db.* TO 'mysite_user'@'localhost';
-- Verify updated privileges
SHOW GRANTS FOR 'mysite_user'@'localhost';
-- Apply changes
FLUSH PRIVILEGES;
-- Exit MariaDB
exit
Post-Implementation Testing
- Test all site functionality thoroughly
- Check plugin operations
- Verify user registrations (if applicable)
- Test content creation and editing
- Monitor error logs for privilege-related issues
When to Grant Additional Privileges
CREATE & ALTER: Required by some cache plugins, backup plugins, or plugins that create custom tables
INDEX: Needed by performance optimization plugins that manage database indexes
DROP: Generally not recommended; only grant if absolutely necessary and you trust the plugin
Database Privilege Restriction Process
✓ Security Benefits
- Reduced attack surface - malicious code can't create backdoor tables
- Limited damage from SQL injection attacks
- Prevents unauthorized table modifications
- Principle of least privilege applied
- Better compliance with security standards