
Secure WordPress After a Hack: 12 Essential Measures
A WordPress hack is never trivial. Even after a successful cleanup, your site remains vulnerable if you do not drastically strengthen its security. The statistics speak for themselves: 73% of cleaned WordPress sites get re-infected within 30 days due to inadequate hardening measures. If you have not cleaned your site yet, start with our complete cleanup guide in 10 steps. This guide presents 12 essential measures to secure WordPress after a hack, covering every layer of protection from the server to the admin interface. Each measure includes the specific commands and configurations to implement.
1. Change All Passwords
The first measure after a hack is to consider that all credentials are compromised. Systematically change every password connected to your WordPress environment.
Passwords to Change
| Account | Where to change | Priority |
|---|---|---|
| WordPress admin accounts | WP-CLI or dashboard | Critical |
| MySQL database | phpMyAdmin or command line | Critical |
| FTP / SFTP | Hosting panel | Critical |
| SSH | SSH key or hosting panel | Critical |
| Hosting panel (cPanel, Plesk) | Hosting interface | High |
| Associated email accounts | Email provider | High |
| CDN and third-party services | Service dashboard | Medium |
WP-CLI Commands
# Change password for all administrators
wp user list --role=administrator --field=ID --path=/var/www/html | while read id; do
NEW_PASS=$(openssl rand -base64 24)
wp user update $id --user_pass="$NEW_PASS" --path=/var/www/html
echo "User $id: password updated"
doneChange Database Credentials
# 1. Change MySQL password
mysql -u root -p -e "ALTER USER 'wp_user'@'localhost' IDENTIFIED BY 'NewComplexPassword_2026!';"
# 2. Update wp-config.php with the new password
wp config set DB_PASSWORD 'NewComplexPassword_2026!' --path=/var/www/htmlUse passwords of at least 20 characters with a mix of uppercase, lowercase, numbers and special characters.
2. Regenerate WordPress Security Keys
WordPress security keys (salts) are used to encrypt cookies and sessions. If an attacker knows them, they can forge valid sessions even after password changes.
# Automatically regenerate all keys
wp config shuffle-salts --path=/var/www/htmlThis command replaces the 8 security constants in wp-config.php:
AUTH_KEYSECURE_AUTH_KEYLOGGED_IN_KEYNONCE_KEYAUTH_SALTSECURE_AUTH_SALTLOGGED_IN_SALTNONCE_SALT
Regenerating keys immediately disconnects all active sessions, including any potentially used by the attacker.
3. Install a WAF (Web Application Firewall)
A WAF filters HTTP traffic before it reaches WordPress, blocking malicious requests upstream.
WAF Options
| Solution | Type | Advantages |
|---|---|---|
| Cloudflare | Cloud (reverse proxy) | DDoS protection included, CDN, free tier |
| Sucuri Firewall | Cloud | WordPress specialized, cleanup included |
| Wordfence | Application plugin | Deep inspection, free tier |
| ModSecurity | Server (Apache/Nginx module) | Performance, OWASP rules |
Cloudflare Configuration (Recommended)
- Create a Cloudflare account and add your domain
- Change nameservers at your registrar
- Enable WordPress security rules:
Cloudflare Dashboard > Security > WAF > Managed Rules
- Enable "Cloudflare WordPress Ruleset"
- Enable "Cloudflare OWASP Core Ruleset"
ModSecurity Configuration (Server)
# Install ModSecurity on Apache
sudo apt install libapache2-mod-security2
sudo a2enmod security2
# Download OWASP CRS rules
cd /etc/modsecurity/
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
# Enable rules in Apache configuration
sudo cat >> /etc/apache2/mods-enabled/security2.conf << 'EOF'
IncludeOptional /etc/modsecurity/coreruleset/crs-setup.conf
IncludeOptional /etc/modsecurity/coreruleset/rules/*.conf
EOF
sudo systemctl restart apache24. Enable Two-Factor Authentication (2FA)
2FA adds an extra security layer by requiring a temporary code in addition to the password. Even if an attacker obtains credentials, they cannot log in without the second factor.
Installation with WP-CLI
# Install the Two-Factor plugin
wp plugin install two-factor --activate --path=/var/www/htmlRecommended Configuration
- Enable TOTP (Time-based One-Time Password) as the primary method
- Configure backup codes in case of phone loss
- Force 2FA for all administrators and editors
Programmatic Configuration (functions.php or mu-plugin)
<?php
// Force 2FA for administrators
add_filter('two_factor_providers', function($providers) {
// Keep only TOTP and backup codes
return array(
'Two_Factor_Totp' => $providers['Two_Factor_Totp'],
'Two_Factor_Backup_Codes' => $providers['Two_Factor_Backup_Codes'],
);
});For optimal security, prefer an authenticator app (Google Authenticator, Authy, 1Password) over SMS, which is vulnerable to SIM swapping.
5. Configure File Permissions
Incorrect file permissions are one of the most common vulnerabilities and make it easier for attackers to inject infected files onto your server. Here are the recommended permissions for WordPress:
Recommended Permissions
| Element | Permission | Command |
|---|---|---|
| Directories | 755 | find . -type d -exec chmod 755 {} \; |
| Files | 644 | find . -type f -exec chmod 644 {} \; |
| wp-config.php | 400 or 440 | chmod 400 wp-config.php |
| .htaccess | 444 | chmod 444 .htaccess |
Apply Correct Permissions
cd /var/www/html
# Directory permissions
find . -type d -exec chmod 755 {} \;
# File permissions
find . -type f -exec chmod 644 {} \;
# Restrictive permissions for wp-config.php
chmod 400 wp-config.php
# Restrictive permissions for .htaccess
chmod 444 .htaccess
# Correct ownership (replace www-data with your web user)
chown -R www-data:www-data /var/www/html/Prevent PHP File Creation in Uploads
Add an .htaccess file in the wp-content/uploads/ directory:
# wp-content/uploads/.htaccess
<FilesMatch "\.(?:php|phtml|php5|php7|phps)$">
Order Allow,Deny
Deny from all
</FilesMatch>For Nginx:
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}6. Disable the Built-in File Editor
The WordPress built-in file editor allows modifying theme and plugin PHP files directly from the dashboard. This is a major entry point for attackers who gain admin access.
# Add to wp-config.php
wp config set DISALLOW_FILE_EDIT true --raw --path=/var/www/htmlThis constant completely disables the theme and plugin editor in the dashboard. For even stricter security, also disable plugin and theme installation from the dashboard:
# Prevent plugin/theme installation and updates from the dashboard
wp config set DISALLOW_FILE_MODS true --raw --path=/var/www/htmlWith DISALLOW_FILE_MODS, updates must be performed exclusively via WP-CLI or SFTP.
7. Protect wp-config.php
The wp-config.php file contains the most sensitive information on your site: database credentials, security keys and site configuration.
Protection via .htaccess (Apache)
# Prevent direct access to wp-config.php
<Files wp-config.php>
Order Allow,Deny
Deny from all
</Files>Protection via Nginx
location ~ /wp-config\.php$ {
deny all;
}Move wp-config.php
WordPress allows placing wp-config.php one level above the web root:
# Move wp-config.php above the web root
mv /var/www/html/wp-config.php /var/www/wp-config.phpWordPress automatically detects the file one level up. This technique adds a layer of protection by placing the file outside the publicly accessible directory.
Add Security Constants
# Force HTTPS
wp config set FORCE_SSL_ADMIN true --raw --path=/var/www/html
# Disable debug in production
wp config set WP_DEBUG false --raw --path=/var/www/html
wp config set WP_DEBUG_DISPLAY false --raw --path=/var/www/html
wp config set WP_DEBUG_LOG false --raw --path=/var/www/html8. Secure the .htaccess File
The .htaccess file controls Apache server behavior. A properly configured .htaccess significantly strengthens security.
Recommended Security Configuration
# Protect .htaccess itself
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
# Prevent directory listing
Options -Indexes
# Block access to sensitive files
<FilesMatch "(^\.ht|wp-config\.php|readme\.html|license\.txt|xmlrpc\.php)">
Order Allow,Deny
Deny from all
</FilesMatch>
# Block PHP execution in wp-includes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# Block PHP execution in wp-includes (alternative)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !^(.*)wp-includes/ms-files.php
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
</IfModule>
# Block suspicious query strings
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
</IfModule>9. Disable XML-RPC
XML-RPC is a legacy communication protocol for WordPress. It is rarely used but represents a major attack vector for brute force attacks and amplified DDoS attacks.
Disable via .htaccess
# Completely block access to xmlrpc.php
<Files xmlrpc.php>
Order Allow,Deny
Deny from all
</Files>Disable via a WordPress Filter
<?php
// Add to functions.php or a mu-plugin
add_filter('xmlrpc_enabled', '__return_false');
// Remove the X-Pingback header
add_filter('wp_headers', function($headers) {
unset($headers['X-Pingback']);
return $headers;
});
// Disable all XML-RPC methods
add_filter('xmlrpc_methods', function($methods) {
return array();
});Disable via Nginx
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}If you use the WordPress mobile app or Jetpack, you will need to keep XML-RPC active. In that case, limit access to specific IP addresses only.
10. Limit Login Attempts
Brute force attacks target the WordPress login page (wp-login.php). Limiting login attempts blocks these attacks effectively.
Install Limit Login Attempts Plugin
# Install and activate the plugin
wp plugin install limit-login-attempts-reloaded --activate --path=/var/www/htmlRecommended Configuration
| Parameter | Recommended value |
|---|---|
| Allowed attempts | 3 |
| Lockout duration | 20 minutes |
| Lockouts before extended block | 3 |
| Extended block duration | 24 hours |
| Email notification | After 2 lockouts |
Additional Protection via .htaccess
# Limit wp-login.php access by IP address
<Files wp-login.php>
Order Deny,Allow
Deny from all
# Allow your IP (replace with your IP)
Allow from 203.0.113.42
Allow from 198.51.100.0/24
</Files>Change the Login URL
Modifying the login page URL significantly reduces automated attacks:
# Install WPS Hide Login
wp plugin install wps-hide-login --activate --path=/var/www/html
# Configure the new login URL
wp option update whl_page 'my-secure-access' --path=/var/www/htmlThe login page will then be accessible at your-site.com/my-secure-access instead of your-site.com/wp-login.php.
11. Configure HTTP Security Headers
HTTP security headers protect against XSS attacks, clickjacking, MIME sniffing and other browser-side threats.
Recommended Headers
| Header | Function | Value |
|---|---|---|
| Content-Security-Policy | Prevents XSS injections | Defines allowed sources |
| X-Content-Type-Options | Blocks MIME sniffing | nosniff |
| X-Frame-Options | Prevents clickjacking | SAMEORIGIN |
| Strict-Transport-Security | Forces HTTPS | max-age=31536000 |
| Referrer-Policy | Controls referrer information | strict-origin-when-cross-origin |
| Permissions-Policy | Restricts browser APIs | As needed |
Apache Configuration (.htaccess)
<IfModule mod_headers.c>
# Force HTTPS for 1 year
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Prevent MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# Referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Permissions policy
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
# Content Security Policy (adjust for your site)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com;"
</IfModule>Nginx Configuration
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
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 Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;Verify Your Headers
After configuration, test your headers with:
# Check HTTP headers
curl -I https://your-site.com
# Or use an online service like securityheaders.com12. Set Up a Monitoring System
The last measure, but arguably the most important in the long run, is to continuously monitor your site to detect any anomaly before it becomes a full breach.
File Integrity Monitoring
# Create a comprehensive monitoring script
cat > /usr/local/bin/wp-security-monitor.sh << 'SCRIPT'
#!/bin/bash
SITE_PATH="/var/www/html"
LOG="/var/log/wp-security.log"
ALERT_EMAIL="admin@your-site.com"
echo "=== Security scan - $(date) ===" >> $LOG
# 1. Verify core file integrity
echo "[Core Checksums]" >> $LOG
wp core verify-checksums --path=$SITE_PATH >> $LOG 2>&1
# 2. Verify plugin integrity
echo "[Plugin Checksums]" >> $LOG
wp plugin verify-checksums --all --path=$SITE_PATH >> $LOG 2>&1
# 3. PHP files in uploads
echo "[PHP in Uploads]" >> $LOG
PHP_IN_UPLOADS=$(find $SITE_PATH/wp-content/uploads/ -name "*.php" -type f 2>/dev/null)
if [ -n "$PHP_IN_UPLOADS" ]; then
echo "ALERT: PHP files found in uploads:" >> $LOG
echo "$PHP_IN_UPLOADS" >> $LOG
echo "$PHP_IN_UPLOADS" | mail -s "SECURITY ALERT: PHP in uploads" $ALERT_EMAIL
fi
# 4. Files modified in the last 24 hours
echo "[Recently modified files]" >> $LOG
find $SITE_PATH -name "*.php" -mtime -1 -type f >> $LOG 2>&1
# 5. New admin accounts
echo "[Admin accounts]" >> $LOG
wp user list --role=administrator --path=$SITE_PATH --format=table >> $LOG 2>&1
echo "=== End of scan ===" >> $LOG
SCRIPT
chmod +x /usr/local/bin/wp-security-monitor.sh
# Schedule a daily scan
(crontab -l 2>/dev/null; echo "0 4 * * * /usr/local/bin/wp-security-monitor.sh") | crontab -External Monitoring Services
| Service | Features | Pricing |
|---|---|---|
| Sucuri SiteCheck | Malware scan, blacklist, anomalies | Free (basic scan) |
| UptimeRobot | Uptime monitoring, real-time alerts | Free (50 monitors) |
| Wordfence Central | Multi-site dashboard, scans | Free (basic features) |
| WPScan | Plugin/theme vulnerabilities | Free (limited API usage) |
Google Search Console Alerts
Configure alerts in Google Search Console to be notified immediately if Google detects:
- Security issues on your site
- Hacked content in the index
- Manual actions related to spam
Summary of All 12 Measures
| Measure | Priority | Difficulty | Impact |
|---|---|---|---|
| 1. Change all passwords | Critical | Low | Very high |
| 2. Regenerate security keys | Critical | Low | High |
| 3. Install a WAF | High | Medium | Very high |
| 4. Enable 2FA | High | Low | Very high |
| 5. Configure file permissions | High | Low | High |
| 6. Disable file editor | High | Low | Medium |
| 7. Protect wp-config.php | High | Low | High |
| 8. Secure .htaccess | Medium | Medium | High |
| 9. Disable XML-RPC | Medium | Low | Medium |
| 10. Limit login attempts | High | Low | High |
| 11. Configure HTTP headers | Medium | Medium | High |
| 12. Set up monitoring | High | Medium | Very high |
Automate Hardening with a Script
To apply the most common measures in a single command:
#!/bin/bash
# WordPress post-hack hardening script
SITE_PATH="/var/www/html"
echo "=== WordPress Hardening ==="
# Regenerate security keys
wp config shuffle-salts --path=$SITE_PATH
echo "[OK] Security keys regenerated"
# Disable file editor
wp config set DISALLOW_FILE_EDIT true --raw --path=$SITE_PATH
echo "[OK] File editor disabled"
# Force HTTPS
wp config set FORCE_SSL_ADMIN true --raw --path=$SITE_PATH
echo "[OK] HTTPS forced for admin"
# Disable debug
wp config set WP_DEBUG false --raw --path=$SITE_PATH
echo "[OK] Debug mode disabled"
# File permissions
find $SITE_PATH -type d -exec chmod 755 {} \;
find $SITE_PATH -type f -exec chmod 644 {} \;
chmod 400 $SITE_PATH/wp-config.php
chmod 444 $SITE_PATH/.htaccess
echo "[OK] File permissions corrected"
# Install and activate security plugins
wp plugin install two-factor --activate --path=$SITE_PATH
wp plugin install limit-login-attempts-reloaded --activate --path=$SITE_PATH
echo "[OK] Security plugins installed"
# Update everything
wp core update --path=$SITE_PATH
wp plugin update --all --path=$SITE_PATH
wp theme update --all --path=$SITE_PATH
echo "[OK] WordPress and extensions updated"
echo "=== Hardening complete ==="Do Not Leave Your Site Unprotected
WordPress security is an ongoing process, not a one-time action. The 12 measures presented in this guide constitute the minimum security baseline after a hack. For optimal protection, combine these measures with regular security audits and active monitoring of vulnerabilities in your WordPress ecosystem.
For professional support, discover our WordPress malware removal service and our WordPress security solutions. Also check our complete post-hack cleanup guide, our catalog of common WordPress malware in 2026, and our WordPress maintenance guide to prevent relapse.
