Introduction
WordPress powers over 43% of all websites, which makes it a prime target for attackers. Hardening WordPress sites isn't about building an impenetrable fortress—it's about reducing the attack surface systematically so that break-ins become costly enough that adversaries move on to easier targets. In this post, we'll walk through practical hardening strategies and the trade-offs involved in each approach, drawing on production experience across multiple e-commerce and SaaS deployments.
Updates — Keep core, themes and plugins current
The most common entry point into a compromised WordPress installation is an outdated plugin or theme. Every security patch that WordPress, a theme vendor, or a plugin maintainer releases is a public announcement: "here's a vulnerability, and here's the fix." Attackers scan for sites still running the unpatched version within hours.
Setting up automatic updates requires balancing velocity against stability. WordPress core can usually be updated automatically without incident, but plugins and themes carry more risk. A poorly maintained plugin update might break a custom integration or introduce a regression that takes down a store during peak hours.
Core updates
Enable automatic updates for WordPress core by adding this to wp-config.php:
define( 'WP_AUTO_UPDATE_CORE', true );This applies security and bug-fix releases automatically. Major version upgrades (e.g., 6.4 to 6.5) still require manual intervention, giving you control when stability matters most.
Plugin and theme updates
For production sites, we recommend a staged approach:
- Staging environment first: A clone of production where updates can be applied and tested before rolling out to live.
- Critical security patches: Deploy immediately. If a vulnerability has a public exploit, delay is dangerous.
- Feature and bug-fix releases: Test in staging for 24–48 hours. Automate on staging; manually trigger on production after verification.
Add this snippet to allow automatic updates for plugins tagged as security-critical:
add_filter( 'auto_update_plugin', function( $update, $item ) {
if ( isset( $item->tags ) && in_array( 'security', $item->tags ) ) {
return true;
}
return $update;
}, 10, 2 );The trade-off: automatic updates reduce manual overhead and response time to vulnerabilities, but they carry a small risk of unintended side effects. A staging environment mitigates that risk effectively.
Login protection — 2FA, rate limits and strong passwords
Brute-force attacks against the WordPress login form are relentless. Attackers use botnets and credential-stuffing lists to try millions of username-password combinations per day. A single weak password or a leaked credential from an unrelated service can compromise your site if login is undefended.
Two-factor authentication
Two-factor authentication (2FA) ensures that stealing a password alone doesn't grant access. Popular plugins like Wordfence and Two Factor integrate TOTP (Time-based One-Time Password) authentication, which users generate from an authenticator app like Authy or Google Authenticator.
Enforcing 2FA across all admin and editor roles is the single most effective login protection measure. Requiring staff to generate a six-digit code every time they log in adds friction, but the security gain is enormous. For large teams, some friction is worth tolerating.
Rate limiting
Rate limiting slows brute-force attacks to a crawl. A common pattern is to lock out an IP address after five failed login attempts within 15 minutes. WordPress.com uses this approach, and it's effective.
Implement rate limiting with a plugin like Limit Login Attempts Reloaded, or build it into your authentication layer:
// Simple rate-limit check for login form
function custom_login_rate_limit( $user, $password ) {
$ip = $_SERVER['REMOTE_ADDR'];
$attempts = get_transient( "login_attempts_{$ip}" );
if ( $attempts && $attempts >= 5 ) {
wp_die( 'Too many login attempts. Try again in 15 minutes.' );
}
if ( ! is_wp_error( $user ) ) {
delete_transient( "login_attempts_{$ip}" );
} else {
set_transient( "login_attempts_{$ip}", ( $attempts ?? 0 ) + 1, 15 * MINUTE_IN_SECONDS );
}
return $user;
}
add_filter( 'authenticate', 'custom_login_rate_limit', 30, 2 );Trade-off: overly aggressive rate limiting can lock out legitimate users who mistype their password, leading to support requests and frustration. A 15-minute lockout after 5 failed attempts is a reasonable balance.
Strong password enforcement
WordPress permits weak passwords by default. Enforce complexity rules: minimum 12 characters, mixed case, numbers, and symbols. Plugins like Force Strong Passwords automate this.
Alternatively, integrate a password strength meter on registration and admin screens to guide users toward better choices without hard rejection.
File permissions — Disable file editing in the admin
WordPress allows administrators to edit plugin and theme code directly from the dashboard under Appearance → Theme File Editor and Plugins → Plugin Editor. This is a significant attack vector: if an attacker gains admin credentials, they can inject malicious PHP into live files instantly.
Disable the theme and plugin editors by adding this line to wp-config.php:
define( 'DISALLOW_FILE_EDIT', true );This removes the menu items and prevents any file editing from the dashboard. Code changes must go through version control, code review, and deployment pipelines—the same process you use for any other application.
File and directory permissions
Set restrictive permissions on core files to prevent unauthorized modification:
- Directories:
755(owner reads, writes, executes; group and others read and execute only). - Files:
644(owner reads and writes; group and others read only). - wp-config.php:
600(owner reads and writes only).
In practice, you'll apply these via your hosting control panel or deployment scripts. Verify them after deployment:
find /var/www/wordpress -type f -exec chmod 644 {} \;
find /var/www/wordpress -type d -exec chmod 755 {} \;
chmod 600 /var/www/wordpress/wp-config.phpTrade-off: restrictive permissions prevent your web server from writing to directories, which can break plugin uploads or automatic updates. If your hosting provider's PHP-FPM process runs as a different user than the files, you may need to adjust. The security benefit—preventing in-place code modification—usually outweighs the operational cost.
Backups — Automated, off-site and tested
No hardening strategy is perfect. A zero-day vulnerability, a compromised dependency, or human error can always breach your defenses. Backups are your failsafe: the ability to restore a clean copy of your site within minutes.
Automated backups
Schedule daily backups of both the database and the /wp-content directory (where posts, media, and plugins live). WordPress core files rarely change after deployment, so backing up only user data and config is often sufficient.
Popular backup plugins like UpdraftPlus or BackWPup automate this:
- Database: MySQL dump via
mysqldumpor the plugin's built-in exporter. - Uploads and plugins: file archive (tar.gz or zip).
- Frequency: daily or more often for high-traffic sites.
- Retention: keep at least 30 days of backups to allow time to detect an intrusion.
Off-site storage
Never store backups on the same server as your WordPress installation. If an attacker gains full server access, they can delete backups alongside the live site. Store backups in a separate location:
- Cloud storage: AWS S3, Google Cloud Storage, or DigitalOcean Spaces with proper access controls.
- Managed backup service: Jetpack Backup, VaultPress, or Backblaze handle off-site storage for you.
- Your own server: A second, isolated server in a different data center.
Configure S3 backups with IAM restrictions so the backup process can only write, never read or delete:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [ "s3:PutObject" ],
"Resource": "arn:aws:s3:::my-backups/wordpress/*"
}
]
}Test your backups
A backup you've never restored is not a backup—it's just data you hope exists. Every month, restore a backup to a staging environment and verify that the site functions correctly. Document the restore process so your team can perform it under pressure.
Trade-off: automated off-site backups add operational cost (storage fees, network bandwidth) and complexity (credentials management, monitoring for backup failures). The cost is trivial compared to the cost of data loss or downtime from a successful attack.
Common mistakes
- Assuming security updates are optional. They're not. Delay updates as a form of risk management only—not as a way to avoid change. Test and deploy them quickly.
- Relying on obscurity instead of hardening. Renaming
wp-admin.phpor changing the admin URL slows automated scanning but not targeted attacks. Focus on foundational controls like 2FA and updates instead. - Forgetting about abandoned plugins and themes. Remove any plugin or theme not actively maintained by its vendor. A dead project won't receive security patches, and it will eventually be compromised.
- Storing backups in the same AWS account. If an attacker gains access to your AWS credentials, they can delete backups from S3. Use a separate AWS account with cross-account backup permissions, or a different provider entirely.
- Never testing the restore process. You won't know if your backups work until you need them and it's too late. Schedule quarterly restore drills.
Key takeaways
- Keep WordPress core, plugins, and themes updated automatically for security patches; test feature updates in staging first.
- Require two-factor authentication for all admin and editor roles; rate-limit login attempts to slow brute force.
- Disable file editing in the admin dashboard and enforce restrictive file permissions; deploy code through version control, not the dashboard.
- Automate daily backups, store them off-site with proper access controls, and test restoration monthly.
- Hardening is layered: no single control is perfect, but multiple controls in sequence make a site too costly to attack.






Comments
Loading comments…