In the vast majority of WordPress site breaches, it is not due to fatal vulnerabilities in the WordPress core, but rather because several critical security boundaries remain open: overly permissive file permissions, unlimited login attempts on the admin panel, and PHP execution capabilities within the upload directory.
Below is a checklist organized from bottom to top that you can implement directly. Each item specifies what to do, why it matters, and how to verify its effectiveness.
1. File Permissions
# Directories 755, Files 644
find /var/www/html -type d -exec chmod 755 {} ;
find /var/www/html -type f -exec chmod 644 {} ;
# Tighten wp-config.php to 600 (contains database credentials)
chmod 600 /var/www/html/wp-config.php
# Change ownership to the web service user
chown -R nginx:nginx /var/www/html
Avoid using 777 permissions. While it may resolve “write failed” errors, the cost is that any local user can modify your site files—this simply shifts the problem to a more severe form.
2. Disable Online File Editing
// wp-config.php
define( 'DISALLOW_FILE_EDIT', true );
If the “Theme File Editor” in the backend is exploited, an attacker with backend access can directly write Webshells. Moreover, a single typo can take down the entire site. This line removes that entry point.
3. Disable PHP Execution in Upload Directory
# Nginx: Do not parse PHP in the upload directory
location ~* ^/wp-content/uploads/.*.php$ {
deny all;
}
# Also block hidden execution methods
location ~* ^/wp-content/uploads/.*.(php|php5|phtml)$ {
return 403;
}
# Apache: Place .htaccess in wp-content/uploads
<Files *.php>
Deny from all
</Files>
This is the highest ROI measure: most upload-based intrusions rely on the ability to execute uploaded PHP files. Disabling execution means that even if a file is uploaded, it remains inert.
4. Backend Login Protection
# Limit login attempts (fail2ban example)
# /etc/fail2ban/jail.local
[wordpress]
enabled = true
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 600
bantime = 3600
- Strong passwords + Two-Factor Authentication (2FA): Mandatory for administrator accounts
- Do not use ‘admin’ as the username: This is the lowest-hanging fruit for brute-force attacks
- Restrict wp-admin access by source: For sites with fixed office IPs, add an IP whitelist directly
# Allow only specific IPs to access the backend
location ~ ^/wp-admin {
allow YourIP;
deny all;
}
5. Close Unnecessary Entry Points
# Disable XML-RPC (if not used)
location = /xmlrpc.php {
deny all;
}
XML-RPC is often used for brute-force amplification—a single request can attempt numerous password combinations. If your site does not need it (no client-side publishing, no Jetpack integration), disable it directly.
// wp-config.php: Force backend to use HTTPS
define( 'FORCE_SSL_ADMIN', true );
6. Reduce Information Exposure
// Remove version number from headers
remove_action( 'wp_head', 'wp_generator' );
# Hide Nginx version number
# In the http block of nginx.conf
server_tokens off;
The value of exposing version numbers lies in this: attackers can directly search for known vulnerabilities based on the version, without needing to probe. This is not defense, but merely increasing the attacker’s cost.
7. Updates and Backups
# Check for pending updates
wp core check-update
wp plugin list --update=available
wp theme list --update=available
# Verify if core files have been tampered with (very useful)
wp core verify-checksums
# Update (backup first)
wp core update
wp plugin update --all
wp core verify-checksums is worth remembering separately: it compares local core files against official checksums, directly telling you which files have been modified. When suspecting a breach, this is the fastest way to locate the issue.
Decision Tree
Hardening Priority (follow this order when time is limited)
├─ ① Disable PHP execution in upload directory ← Highest ROI, takes ~10 mins
├─ ② Tighten file permissions + set wp-config to 600
├─ ③ Enable DISALLOW_FILE_EDIT
├─ ④ Backend login protection (strong password + 2FA + rate limiting)
├─ ⑤ Disable XML-RPC
└─ ⑥ Regular updates + verified recoverable backups
Suspected Breach
├─ wp core verify-checksums → Check if core files were modified
├─ find -mtime -7 to find recent changes → Locate intrusion timeframe
└─ Check cron jobs and accounts → Look for persistent backdoors
Scenario Analysis: Strong Password, Yet Still Compromised
Symptom: A WordPress site suddenly displays abnormal promotional content.
There are no suspicious operations in the backend, and the password appears complex. Initial suspicion points to account leakage.
- Don’t rush to change the password; first check login logs for anomalies → Review backend login records and security logs.
Discovering a large volume of failed attempts in a short period indicates automated brute-forcing rather than credential leakage. - Confirm if the login entry has protection → The backend address was fully open,
with no frequency limits, effectively allowing unlimited attempts. - Stop the bleeding: Secure the login entry first → Restrict backend access sources and add frequency limits.
Brute-forcing stops immediately. Do this step first, because without closing the entry, subsequent hardening measures will be continuously probed. - Determine what the attacker has accessed → Check recently added files and accounts,
focusing on whether there are executable files in the upload directory. - Close the gap: Disable script execution in the upload directory → This is the most common path attackers use to gain further privileges after accessing the backend.
Block script parsing for this directory at the web layer. - Implement long-term measures → Enable 2FA, disable online editing, and adjust account policies to least privilege.
# Typical characteristics of backend brute-forcing: massive POST requests to the same path in a short time
awk '{print $1, $7}' access.log | grep -i 'wp-login|xmlrpc' | sort | uniq -c | sort -rn | head
# Nginx layer: Limit backend access frequency
# limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
# location = /wp-login.php { limit_req zone=login burst=5 nodelay; include fastcgi_params; ... }
# Disable PHP execution in upload directory (most critical action)
# location ~* ^/wp-content/uploads/.*.php$ { deny all; }
# Disable online file editing in backend
# In wp-config.php: define( 'DISALLOW_FILE_EDIT', true );
Root Cause: The backend login entry had neither frequency limits nor source restrictions.
Crucially, the most important remediation measure—disabling script execution in the upload directory—was not configured at the time,
allowing the attacker to directly write executable files after gaining backend access.
Two Key Takeaways:
- The order of hardening is “close the entry first, then deepen defenses.”
Without restricting backend access frequency, remaining hardening measures will be systematically mapped out during continuous probing. - Disabling script execution in the upload directory has higher priority than most other hardening items.
It corresponds precisely to the path “how to further compromise the server after the backend falls,”
offering extremely low cost yet frequently overlooked.
Comparison of Impact and Cost for Seven Hardening Measures
| Hardening Measure | Blocks What | Consequence of Not Doing | Cost |
|---|---|---|---|
| File Permissions 644/755 | Webshell injection | A single upload point grants write access | Negligible |
| Disable Online Editing | Direct modification of theme files via backend | Backend access equals server file write access | Offline theme editing required |
| Disable PHP Execution in Upload Dir | Execution of image-malware uploads | Highest frequency breach path | Low |
| Backend Rate Limiting / 2FA | Brute-force login | Weak passwords lead to immediate compromise | One extra step for login |
| Disable XML-RPC | Brute-force amplification and probing | DDoS traffic, username enumeration | Some clients may fail |
| Hide Version Number | Vulnerability scanning by version | Becomes an easy target for automated scanners | Low |
| Auto Updates + Backups | Exploitation of known vulnerabilities | Patches released but never applied | Must be able to rollback before updating |
If you can only do three things, do these: Disable PHP execution in the upload directory, protect backend login, and enable auto-updates with rollback-capable backups.
They correspond respectively to “the most commonly exploited entry,” “the easiest-to-guess entry,” and “the escape route in case of incident.”
It is worth noting the DISALLOW_FILE_EDIT measure. It does not block external attackers initially,
but rather the step taken after “the backend has already fallen.” Once an attacker obtains admin credentials, the fastest way to gain server control
is to modify theme files in the backend to write a shell; disabling online editing cuts off this path.
# Confirm upload directory has no executable permissions (Nginx layer)
location ~* ^/wp-content/uploads/.*.php$ {
deny all;
}
8 Common Pitfalls
- Using 777 to fix permission errors: Effectively opens site files for writing to everyone.
- Adding security plugins without changing configurations: Plugins cannot manage server-level issues like file permissions or upload directory execution.
- Leaving the backend file editor enabled: A single character error causes a white screen, and you may lose backend access.
- Allowing PHP execution in the upload directory: This is the decisive condition for successful upload-based intrusions.
- Using ‘admin’ as the username: Lowers the difficulty of brute-forcing to the minimum.
- Ignoring XML-RPC: Leaving it open when unused creates an entry point for brute-force amplification.
- Never updating plugins: Plugin vulnerabilities are a primary source of breaches, far more common than core vulnerabilities.
- Having backups but never verifying recovery: Discovering backups are unusable during an actual incident is the worst-case scenario.
Real Customer Feedback (From Cloud Market Reviews and Consultations for Similar Products)
The following excerpts are from real buyer reviews and consultations for similar O&M services on Alibaba Cloud / Tencent Cloud / Huawei Cloud Cloud Markets (service providers include our store and peers, provided solely for reference of real feedback, not fictitious tickets from this site):
10***09 2024-03-18 5.0 Convenient & Stable | Clean Interface | Comprehensive After-sales (User did not fill in review content)