This guide walks you through hardening an Nginx server that hosts WordPress and phpMyAdmin. You will lock down file permissions, disable risky PHP functions, protect sensitive directories, add security headers, and set up basic rate limiting. By the end, your setup will block common attack paths like direct PHP execution in uploads, brute-force login attempts, and information leaks.
What You’ll Need
- Root or sudo access to your Linux server (Ubuntu/Debian commands shown, adjust for your distro).
- Nginx installed and serving WordPress already.
- PHP-FPM installed and configured for WordPress.
- phpMyAdmin installed and accessible (usually at a subdirectory like
/phpmyadmin). - SSL/TLS certificate already configured (use Let’s Encrypt if not).
- Basic familiarity with editing files in a terminal (nano or vim).
Step-by-Step
- Backup your current configuration.
Copy your Nginx site config and phpMyAdmin config to a safe location:sudo cp /etc/nginx/sites-available/your-site /etc/nginx/sites-available/your-site.baksudo cp /etc/phpmyadmin/config.inc.php /etc/phpmyadmin/config.inc.php.bak - Set correct file permissions for WordPress.
Run these commands to make files read-only and directories executable, while allowing WordPress to write towp-content(adjust if you use a different user):sudo find /var/www/html -type f -exec chmod 644 {} \;sudo find /var/www/html -type d -exec chmod 755 {} \;sudo chown -R www-data:www-data /var/www/html/wp-content
This prevents PHP scripts from modifying core files. - Block PHP execution in uploads and other writable directories.
Edit your Nginx server block (usually in/etc/nginx/sites-available/your-site). Inside theserverblock, add:location ~* /(?:uploads|files|wp-content)/.*\.php$ { deny all; }This stops attackers from uploading a PHP shell and executing it.
- Disable dangerous PHP functions.
Edit your PHP-FPM pool config (e.g.,/etc/php/8.1/fpm/php.ini). Find thedisable_functionsdirective and set it to:disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,show_source
Save and restart PHP-FPM:sudo systemctl restart php8.1-fpm(adjust version). - Protect phpMyAdmin with additional Nginx rules.
In the same server block, add a location block for phpMyAdmin that restricts access to your IP or requires HTTP auth. For IP restriction:location ^~ /phpmyadmin { allow 192.168.1.100; # your IP deny all; # remaining phpMyAdmin config }Better: use HTTP basic auth. Create a password file:
sudo htpasswd -c /etc/nginx/.htpasswd admin
Then in the location block:location ^~ /phpmyadmin { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; } - Hide Nginx and PHP version numbers.
In/etc/nginx/nginx.conf, setserver_tokens off;. In your PHP-FPM config, setexpose_php = Off. Restart both services. - Add security headers.
In your server block, add:add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header X-XSS-Protection "1; mode=block" always;
These prevent clickjacking, MIME sniffing, and other client-side attacks.
- Limit login attempts with rate limiting.
Define a limit zone inhttpblock of nginx.conf:limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
Then in your server block, apply it to wp-login.php and phpMyAdmin:location = /wp-login.php { limit_req zone=login burst=2 nodelay; # include fastcgi params } location ^~ /phpmyadmin { limit_req zone=login burst=2 nodelay; # ... }This slows down brute-force attacks.
- Disable directory listing.
Addautoindex off;in your server block or in alocation /block. This prevents Nginx from listing directory contents if no index file exists. - Restrict access to sensitive WordPress files.
Add these location blocks to deny direct access to config files and scripts:location ~* ^/\.(?!well-known).* { deny all; } location ~* (wp-config\.php|xmlrpc\.php|readme\.html|license\.txt) { deny all; }Note: the first rule allows
.well-knownfor Let’s Encrypt renewals. - Test and reload Nginx.
Runsudo nginx -tto check for syntax errors, thensudo systemctl reload nginx.
Common Mistakes
- Blocking PHP in uploads but forgetting other writable dirs. Also block
wp-content/cache,wp-content/pluginsif you must, but careful not to break plugin functionality. - Using
deny allon the entirewp-contentdirectory. That breaks CSS/JS/images. Only block PHP execution. - Forgetting to restart PHP-FPM after changing
disable_functions. Changes only take effect after restart. - Putting security headers in the wrong block. They must be inside
serverorlocationthat serves your site, not inhttponly. - Rate limiting too aggressively. A rate of 1r/m might block legitimate users. Start with 5r/m and adjust.
- Not testing after each change. A small typo in Nginx config can take the whole site down.
Tips
- Use a dedicated Nginx config file for phpMyAdmin (e.g.,
/etc/nginx/conf.d/phpmyadmin.conf) to keep things organized. - Consider using a Web Application Firewall (WAF) like ModSecurity or a cloud WAF for added protection.
- Regularly update WordPress, plugins, phpMyAdmin, and PHP to patch known vulnerabilities.
- Use a strong password for phpMyAdmin and change the default
rootMySQL user password. - Enable 2FA for WordPress admin accounts via a plugin like WP 2FA.
- Monitor your Nginx access logs for suspicious patterns like repeated 403s on wp-login.php.
Troubleshooting
- Site returns 403 Forbidden after adding deny rules. Check if you accidentally blocked PHP in a needed directory. Look at the Nginx error log:
sudo tail -f /var/log/nginx/error.log. Adjust the location regex. - phpMyAdmin shows a blank page or 404. Ensure the
location ^~ /phpmyadminblock includes proper FastCGI parameters. If you added auth, make sure the .htpasswd file path is correct and readable by Nginx. - WordPress cannot upload files. Check that
wp-contentownership is correct. You may need to set the group towww-dataand add your user to that group. - Rate limiting blocks legitimate users. Increase the rate or burst value. You can test by temporarily removing the limit and watching logs.
- Security headers not showing in browser. Clear your browser cache or use curl -I to check. Ensure the
add_headerdirectives are in the correct server block. - After changing PHP-FPM, site shows 502 Bad Gateway. The PHP-FPM service may have failed to restart. Check status:
sudo systemctl status php8.1-fpmand look for syntax errors in php.ini.
Frequently Asked Questions
Do I need to secure phpMyAdmin if it’s only accessible from my IP?
Yes, because IP addresses can be spoofed or your IP might change. Adding HTTP auth or a VPN is a good second layer.
Will blocking PHP in uploads break my WordPress themes?
No, themes use PHP files that are executed via WordPress’s main index.php, not directly in the uploads folder. Blocking direct access to PHP in uploads only stops malicious scripts.
Can I use fail2ban instead of Nginx rate limiting?
Yes, fail2ban can monitor logs and block IPs after repeated failures. It works well alongside Nginx rate limiting.
Is it safe to disable curl_exec in PHP?
If your WordPress plugins or themes rely on cURL to make external HTTP requests (e.g., for API integrations), disabling it may break them. Test thoroughly after disabling.
Should I change the default phpMyAdmin URL?
It’s a good idea to move phpMyAdmin to a non-standard path like /db-admin to reduce automated scans. Update your Nginx location block accordingly.
Do I need to configure SSL for phpMyAdmin separately?
No, if your entire site is under HTTPS, phpMyAdmin will be served over the same SSL certificate. Just ensure your Nginx server block listens on port 443.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Access the DOM in Angular Safely
- Websites Create QR Code? How It Works
- Evaluate Private Cloud AI Security Operations Platforms
- Space Engineers Server Name & Description Setup
- Tongue Escape Events Schedule: Full Guide
- f/2.8 Telephoto Zoom: What to Consider