Nginx serves as the front door to your WordPress site. Every request passes through it, making Nginx configuration critical to your security posture. Default configurations prioritize compatibility over security, so hardening requires deliberate adjustments. This guide covers essential Nginx security settings for WordPress, from HTTP headers to access restrictions, providing practical configurations you can implement immediately.
- Security headers add protection layers without changing WordPress code
- Restricting access to sensitive files prevents information disclosure
- Rate limiting helps protect against brute force attacks
- Always test configuration changes in a staging environment first
I. Essential Security Headers
HTTP headers instruct browsers how to handle your content. Security headers add protection layers against common attacks.
A. X-Content-Type-Options
- Purpose: Prevents browsers from MIME-sniffing away from declared content type.
- Configuration:
add_header X-Content-Type-Options "nosniff" always; - Why it matters: Prevents attacks where browsers interpret files as different types than served.
B. X-Frame-Options
- Purpose: Protects against clickjacking by controlling if your site can be framed.
- Configuration:
add_header X-Frame-Options "SAMEORIGIN" always; - Options: DENY (never allow framing), SAMEORIGIN (allow from same domain only).
C. X-XSS-Protection
- Purpose: Enables browser's built-in XSS filtering (legacy but still useful).
- Configuration:
add_header X-XSS-Protection "1; mode=block" always; - Note: Modern browsers use CSP instead, but this helps older browsers.
D. Referrer-Policy
- Purpose: Controls how much referrer information is shared with linked sites.
- Configuration:
add_header Referrer-Policy "strict-origin-when-cross-origin" always; - Balance: Maintains functionality while limiting information leakage.
E. Permissions-Policy
- Purpose: Controls which browser features your site can use (camera, microphone, geolocation).
- Configuration:
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; - Effect: Prevents exploitation of features your site does not need.
II. Hiding Sensitive Information
A. Hide Server Version
- Why: Version disclosure helps attackers identify vulnerable software.
- Configuration:
server_tokens off;in http block. - Effect: Removes Nginx version from Server header and error pages.
B. Remove PHP Version
- Configuration: In php.ini, set
expose_php = Off - Effect: Removes X-Powered-By header revealing PHP version.
III. WordPress-Specific Protections
A. Block Access to Sensitive Files
Prevent direct access to files that should not be served publicly.
- wp-config.php: Contains database credentials. Must never be accessible.
- .htaccess: Apache files that might exist in WordPress installations.
- Hidden files: Dot files like .git, .env should be blocked.
B. Restrict wp-includes
- Block PHP execution: No PHP files in wp-includes should be accessed directly.
- Allow static files: CSS, JavaScript, and images in wp-includes are needed.
C. Protect Uploads Directory
- Block PHP execution: Uploaded files should never execute as PHP.
- Configuration: Return 403 for any .php file request in uploads directory.
- Why critical: Prevents uploaded malicious PHP from executing.
D. Limit wp-admin Access
- IP whitelisting: If possible, restrict admin to known IP addresses.
- Rate limiting: Slow down brute force attempts on wp-login.php.
IV. Rate Limiting Configuration
Rate limiting slows attackers while allowing legitimate users normal access.
A. Login Endpoint Protection
- Target: /wp-login.php and /xmlrpc.php are primary brute force targets.
- Limit zone: Define limit zone in http block, apply in location block.
- Reasonable limits: 1 request per second with small burst allowance.
B. XML-RPC Considerations
- If not used: Block XML-RPC entirely. Most sites do not need it.
- If used: Apply strict rate limiting. XML-RPC is a common attack vector.
- Jetpack note: Jetpack requires XML-RPC. Rate limit instead of blocking.
V. SSL/TLS Configuration
A. Modern TLS Only
- Disable old protocols:
ssl_protocols TLSv1.2 TLSv1.3; - Why: TLS 1.0 and 1.1 have known vulnerabilities. All modern browsers support 1.2+.
B. Strong Cipher Suites
- Server preference:
ssl_prefer_server_ciphers on; - Modern ciphers: Use Mozilla's SSL Configuration Generator for current recommendations.
C. HSTS Header
- Purpose: Forces browsers to always use HTTPS for your domain.
- Configuration:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - Caution: Only enable after confirming HTTPS works perfectly everywhere.
VI. Additional Protections
A. Limit Request Body Size
- Configuration:
client_max_body_size 64M; - Balance: Large enough for legitimate uploads, small enough to limit abuse.
B. Connection Limits
- Limit connections per IP: Prevents single IP from consuming all resources.
- Balance: High enough not to affect legitimate users sharing IPs (offices, NAT).
C. Block Bad User Agents
- Known scanners: Block user agents associated with automated vulnerability scanners.
- Empty agents: Consider blocking requests with no User-Agent header.
- Limitation: User agents are easily spoofed, so this is not strong protection.
VII. Testing Your Configuration
- Test syntax: Run
nginx -tbefore reloading to catch syntax errors. - Check headers: Use browser DevTools or curl to verify headers are applied.
- Security scanners: Use tools like Mozilla Observatory or Security Headers to audit.
- SSL test: SSL Labs SSL Test evaluates your TLS configuration.
VIII. Configuration Changes Process
- Backup first: Copy existing configuration before making changes.
- Test in staging: Apply changes to staging environment first.
- Verify functionality: Test WordPress admin, login, uploads after changes.
- Monitor logs: Watch access and error logs after deploying changes.
- Gradual rollout: Apply to production after confirming no issues in staging.
IX. Conclusion
Nginx hardening adds essential security layers to your WordPress hosting stack. Security headers protect visitors' browsers from various attacks. File access restrictions prevent information disclosure and block malicious file execution. Rate limiting slows brute force attacks. Strong TLS configuration protects data in transit. These configurations work together to create defense in depth, where multiple layers of security reduce risk even if one layer is bypassed. Always test changes thoroughly before applying to production, and regularly audit your configuration as new best practices emerge.
Which Nginx security measure was most impactful for your site? Share your experience!