Overview
This comprehensive guide demonstrates how to implement robust security measures for WordPress sites using Nginx directives. By implementing server-level security controls, you can block malicious requests before they reach your WordPress application, significantly reducing the attack surface and improving overall site security.
- Block bad requests at the server level before they reach WordPress
- Prevent unauthorized access to sensitive files and directories
- Disable PHP execution in critical directories to prevent code injection
- Protect against SQL injection, file injection, and common exploits
- Block spam bots and malicious user agents
Security Architecture Flow
Security Layers Implemented
File Protection
Deny access to sensitive configuration files, readme files, and .ini files
PHP Execution Control
Prevent PHP execution in uploads, plugins, and themes directories
Injection Prevention
Block SQL and file injection attempts through query string filtering
Bot Protection
Block known malicious bots and vulnerability scanners
Step-by-Step Implementation Guide
1Navigate to Nginx Includes Directory
First, ensure you are in the correct directory where Nginx include files are stored.
cd /etc/nginx/includes/2Create Security Directives Configuration File
Create a new configuration file to store all security directives.
sudo nano nginx_security_directives.conf3Add Security Directives Configuration
Copy and paste the following comprehensive security directives into the
nginx_security_directives.conf file:
Complete Nginx Security Directives 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 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;
}4Save the Configuration File
After pasting the directives, save and close the file (in nano: Ctrl+X, then Y, then Enter).
5Get the Full Path to the Configuration File
Use the readlink command to obtain the absolute path to your newly created configuration
file.
readlink -f nginx_security_directives.conf/etc/nginx/includes/nginx_security_directives.conf
6Navigate to Sites Available Directory
Change to the directory containing your site's Nginx server block configuration.
cd /etc/nginx/sites-available/7Edit Your Site's Server Block Configuration
Open your site's Nginx configuration file for editing.
sudo nano example.com.conf8Include the Security Directives File
Add the include directive in your server block. Place it directly above the PHP processing location block.
Placement in Server Block
# Place ABOVE the php processing location block
include /etc/nginx/includes/nginx_security_directives.conf;location ~ \.php$ {) and
add the include directive immediately before it.
9Test Nginx Configuration
Always test your Nginx configuration before reloading to catch any syntax errors.
sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
10Reload Nginx
If the syntax test is successful, reload Nginx to apply the new configuration.
sudo systemctl reload nginxTesting the Security Implementation
Test 1: Verify Readme File Protection
Before implementing security directives, WordPress readme files were publicly accessible. Let's verify they are now blocked.
Test Access to Readme File:
Open your browser and navigate to:
https://example.com/readme.htmlTest 2: Verify PHP Execution is Blocked in Plugins Directory
Let's create a test PHP file in the plugins directory to verify that PHP execution is properly blocked.
Navigate to Your Site's Root Directory:
cd /var/www/example.com/Change to WordPress Content Directory:
cd public_html/wp-content/Navigate to Plugins Directory:
cd pluginsCreate a Test PHP Info File:
sudo nano test556.phpAdd PHP Info Code:
<?php
phpinfo();
?>Save and close the file.
Test in Browser:
Navigate to the test file in your browser:
https://example.com/wp-content/plugins/test556.phpAllowing PHP Execution for Specific Plugins
Understanding the Security Approach
The security model follows these principles:
- Default Deny: All PHP execution in plugins directory is blocked by default
- Explicit Allow: Create specific exceptions only for legitimate plugin files that require PHP execution
- Path-Specific: Exceptions are granted only to the exact file path, not the entire directory
Exception Handling Flow
How to Identify Required PHP Files
When a plugin needs to execute a PHP file, you can identify it through:
- Check your Nginx access and error log files
- Check your browser's address bar (the file path may be visible)
- Review plugin documentation
- Contact the plugin developer
Step-by-Step: Creating a PHP Execution Exception
1Determine the Full File Path
Use the readlink command to get the absolute path to the plugin file:
readlink -f test556.php/var/www/example.com/public_html/wp-content/plugins/test556.php
2Open Your Site's Server Block Configuration
sudo nano /etc/nginx/sites-available/example.com.conf3Locate the PHP Processing Block
Find the location context that processes PHP files (typically looks like this):
location ~ \.php$ {
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;
}4Add Exception Location Block
Important: Add the new location block immediately after (underneath) the closing brace of the PHP processing block.
The path in the location directive must be relative to the root directive defined in your server block. Do NOT use the absolute file system path.
Understanding Root-Relative Paths
If your server block defines:
root /var/www/example.com/public_html;Then the location path should be relative to this root:
Correct Exception Configuration
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-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}Absolute Path:
/var/www/example.com/public_html/wp-content/plugins/test556.phpRoot Directive:
/var/www/example.com/public_htmlRelative Path for Location:
/wp-content/plugins/test556.php
Complete Server Block Example
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php index.html;
# Security directives include
include /etc/nginx/includes/nginx_security_directives.conf;
# PHP processing block
location ~ \.php$ {
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;
}
# Exception for specific plugin file
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-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
# Other configurations...
}5Test and Apply Configuration
Test the Nginx configuration:
sudo nginx -tReload Nginx to apply changes:
sudo systemctl reload nginxRestart PHP-FPM to ensure all changes take effect:
sudo systemctl restart php8.3-fpm6Test the Exception
Navigate to the file in your browser:
https://example.com/wp-content/plugins/test556.phpClean Up Test Files
After testing, remove the test PHP file:
cd /var/www/example.com/public_html/wp-content/plugins/sudo rm test556.phpRemove the test exception from your server block:
sudo nano /etc/nginx/sites-available/example.com.confDelete the location block for test556.php, then test and reload:
sudo nginx -tsudo systemctl reload nginxSecurity Directives Breakdown
| Protection Type | What It Does | Example |
|---|---|---|
| File Access Control | Denies access to sensitive configuration files | wp-config.php, readme.txt, license.txt |
| PHP Execution Prevention | Blocks PHP execution in critical directories | uploads/, plugins/, themes/ |
| Core File Protection | Protects WordPress core files from direct access | wp-includes/*.php files |
| Development File Blocking | Prevents access to development dependencies | composer.json, package.json, /vendor/ |
| HTTP Method Filtering | Blocks dangerous HTTP methods | TRACE, DELETE, TRACK |
| User Agent Filtering | Blocks known malicious scanners and bots | nikto, sqlmap, masscan |
Best Practices and Recommendations
Security Best Practices:
- Regular Updates: Keep your security directives updated as new threats emerge
- Log Monitoring: Regularly check your Nginx error logs for blocked attempts
- Minimal Exceptions: Only create exceptions for files that absolutely require PHP execution
- Documentation: Document all exceptions you create and why they are necessary
- Testing: Always test in a staging environment before applying to production
Common Pitfalls to Avoid:
- Never disable all PHP blocking in the plugins directory – create specific exceptions instead
- Don't forget to test configuration with
sudo nginx -tbefore reloading - Remember to use root-relative paths in location directives, not absolute file system paths
- Don't skip restarting PHP-FPM after making PHP-related changes
Monitoring and Maintenance
Check Nginx Access Logs
Monitor which requests are being blocked:
sudo tail -f /var/log/nginx/access.logCheck Nginx Error Logs
View detailed error information:
sudo tail -f /var/log/nginx/error.logCheck PHP-FPM Logs
Monitor PHP-specific issues:
sudo tail -f /var/log/fpm-php.example.com.logTroubleshooting
Issue: 403 Forbidden on Legitimate Plugin
Solution: Create a specific location exception for the required plugin file as described in the "Allowing PHP Execution for Specific Plugins" section.
Issue: 404 Not Found Instead of 403 Forbidden
Possible Cause: The location directive path may be incorrect. Verify that the path is relative to your root directive.
Issue: Configuration Test Fails
Solution: Carefully review the error message from sudo nginx -t. Common
issues include:
- Missing semicolons
- Mismatched braces { }
- Incorrect file paths in include directives
Issue: Changes Not Taking Effect
Solution: Ensure you've reloaded Nginx and restarted PHP-FPM:
sudo systemctl reload nginx && sudo systemctl restart php8.3-fpm
Summary and Conclusion
What We've Accomplished:
- Created comprehensive Nginx security directives to protect WordPress
- Implemented server-level blocking of malicious requests
- Prevented PHP execution in critical directories
- Protected sensitive configuration and readme files
- Learned how to create targeted exceptions for legitimate plugin needs
- Established a security model based on "default deny, explicit allow"
By implementing these Nginx security directives, you have added a robust layer of protection to your WordPress site at the web server level. This approach stops attacks before they can even reach your WordPress application, significantly reducing server load from malicious traffic and protecting against common exploits.