WordPress Security Hardening Guide

WordPress powers 40% of the web, making it a prime target for attackers exploiting misconfigured installations, vulnerable plugins, and weak admin credentials.

WordPress powers a large portion of websites on the internet, from personal blogs to large e-commerce platforms. This ubiquity makes it an attractive target for attackers. Automated bots constantly scan for vulnerable WordPress installations, outdated plugins, and weak credentials. A compromised WordPress site can serve malware, steal customer data, send spam, or become part of a botnet. This guide covers essential WordPress security hardening measures, from core configuration to plugin assessment and admin protection, and explains how agentic penetration testing can identify vulnerabilities before attackers exploit them.

Understanding WordPress Security Risks

WordPress security challenges stem from several factors:

  • Plugin ecosystem: The large plugin ecosystem varies widely in code quality and security practices
  • Shared hosting environments: Many WordPress sites run on shared hosts with limited security controls
  • Default configurations: Out-of-the-box WordPress settings prioritize ease of use over security
  • Update lag: Many sites run outdated WordPress core, themes, or plugins
  • Weak credentials: Default usernames and weak passwords remain common

Common attack vectors include:

  • Brute force attacks against wp-admin and XML-RPC
  • SQL injection through vulnerable plugins
  • Cross-site scripting (XSS) in themes and plugins
  • File inclusion vulnerabilities allowing remote code execution
  • Privilege escalation from subscriber to admin

WordPress Core Security Configuration

Start with securing the WordPress core installation itself.

wp-config.php Hardening

The wp-config.php file controls critical WordPress settings. Move it above the web root if possible, and add these security configurations:

<?php
// Disable file editing in admin panel
define('DISALLOW_FILE_EDIT', true);
 
// Limit post revisions to reduce database bloat and attack surface
define('WP_POST_REVISIONS', 5);
 
// Force SSL for admin
define('FORCE_SSL_ADMIN', true);
 
// Disable XML-RPC if not needed (reduces brute force attack surface)
// Handle in .htaccess or use a plugin for more granular control
 
// Authentication keys and salts (generate unique ones)
// https://api.wordpress.org/secret-key/1.1/salt/
define('AUTH_KEY',         'your-unique-phrase-here');
define('SECURE_AUTH_KEY',  'your-unique-phrase-here');
define('LOGGED_IN_KEY',    'your-unique-phrase-here');
define('NONCE_KEY',        'your-unique-phrase-here');
define('AUTH_SALT',        'your-unique-phrase-here');
define('SECURE_AUTH_SALT', 'your-unique-phrase-here');
define('LOGGED_IN_SALT',   'your-unique-phrase-here');
define('NONCE_SALT',       'your-unique-phrase-here');
 
// Database table prefix - change from default 'wp_'
$table_prefix = 'wp_a1b2c3_';
 
// Disable debug mode in production
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
 
// Automatic updates for security releases
define('WP_AUTO_UPDATE_CORE', 'minor');

.htaccess Security Rules

Add these directives to your .htaccess file:

# Protect wp-config.php
<files wp-config.php>
order allow,deny
deny from all
</files>
 
# Protect .htaccess
<files .htaccess>
order allow,deny
deny from all
</files>
 
# Disable directory browsing
Options -Indexes
 
# Block access to sensitive files
<FilesMatch "^.*(error_log|wp-config\.php|php\.ini|\.[hH][tT][aApP].*)$">
Order deny,allow
Deny from all
</FilesMatch>
 
# Protect 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>
 
# Disable XML-RPC
<Files xmlrpc.php>
order deny,allow
deny from all
</Files>
 
# Limit login attempts by blocking common bad actors (requires mod_rewrite)
# Consider using a plugin for more sophisticated rate limiting

Database Security

Secure your WordPress database:

-- Create a dedicated user with minimal privileges
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'strong_password_here';
 
-- Grant only necessary permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON wordpress_db.* TO 'wp_user'@'localhost';
 
-- Don't grant FILE, PROCESS, or administrative privileges
-- Avoid granting ALTER, DROP, CREATE unless during updates
 
FLUSH PRIVILEGES;

Plugin Security Assessment

Plugins are the most common source of WordPress vulnerabilities. Implement a rigorous assessment process.

Before Installing Plugins

Evaluate plugins before installation:

  1. Check last update date: Plugins not updated in 2+ years may have unpatched vulnerabilities
  2. Review active installations: Higher numbers suggest community vetting
  3. Read recent reviews: Look for security-related complaints
  4. Check the changelog: Active security patching is a good sign
  5. Search vulnerability databases: Check WPScan, CVE databases, and security blogs

Auditing Installed Plugins

Regularly audit your plugin inventory:

# List all plugins with their status
wp plugin list --format=table
 
# Check for available updates
wp plugin update --all --dry-run
 
# Identify inactive plugins (remove these)
wp plugin list --status=inactive
 
# Check plugin checksums against WordPress.org
wp plugin verify-checksums --all

Plugin Security Checklist

For each plugin, verify:

  • Plugin is from a reputable source (WordPress.org, known vendor)
  • Plugin is actively maintained (updated within last 6 months)
  • Plugin has no known unpatched vulnerabilities
  • Plugin uses nonces for form submissions
  • Plugin properly sanitizes and escapes output
  • Plugin uses prepared statements for database queries
  • Plugin follows WordPress capability checks

Removing Unnecessary Plugins

Every installed plugin expands your attack surface:

# Deactivate and delete unused plugins
wp plugin deactivate plugin-name
wp plugin delete plugin-name
 
# List plugins and their sizes
wp plugin list --fields=name,status --format=csv | while read line; do
  name=$(echo $line | cut -d',' -f1)
  du -sh wp-content/plugins/$name 2>/dev/null
done

Admin Hardening

The WordPress admin area is a primary target. Implement layered protection.

Change Default Username

Never use "admin" as a username:

# Create a new admin user
wp user create newadmin newadmin@example.com --role=administrator --user_pass=strong_password
 
# Reassign posts from old admin
wp post list --author=1 --format=ids | xargs -I {} wp post update {} --post_author=NEW_USER_ID
 
# Delete the old admin user
wp user delete admin --reassign=NEW_USER_ID

Implement Two-Factor Authentication

Add 2FA for all admin and editor accounts. Use plugins like:

  • Wordfence (includes 2FA)
  • Two-Factor (official WordPress plugin)
  • Google Authenticator

Limit Login Attempts

Protect against brute force attacks:

// In your theme's functions.php or a custom plugin
function custom_limit_login_attempts($user, $username, $password) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $lockout_duration = 15 * MINUTE_IN_SECONDS;
    $max_attempts = 5;
    
    $attempts = get_transient('login_attempts_' . $ip);
    
    if ($attempts >= $max_attempts) {
        return new WP_Error(
            'too_many_attempts',
            'Too many failed login attempts. Please try again later.'
        );
    }
    
    return $user;
}
add_filter('authenticate', 'custom_limit_login_attempts', 30, 3);
 
function track_failed_login($username) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $attempts = get_transient('login_attempts_' . $ip) ?: 0;
    set_transient('login_attempts_' . $ip, $attempts + 1, 15 * MINUTE_IN_SECONDS);
}
add_action('wp_login_failed', 'track_failed_login');

Change Login URL

Move the login page from the default /wp-admin:

// Using a plugin like WPS Hide Login is recommended
// Or implement manually with careful testing
 
function custom_login_url($login_url, $redirect, $force_reauth) {
    return home_url('/secure-login/');
}
add_filter('login_url', 'custom_login_url', 10, 3);

Restrict Admin Access by IP

If your admin users have static IPs:

# In .htaccess within wp-admin directory
<Files *.php>
order deny,allow
deny from all
allow from YOUR.IP.ADDRESS.HERE
allow from ANOTHER.ALLOWED.IP
</Files>

Disable User Enumeration

Prevent attackers from discovering valid usernames:

// Block author archives that reveal usernames
function disable_author_archives() {
    if (is_author()) {
        global $wp_query;
        $wp_query->set_404();
        status_header(404);
        nocache_headers();
    }
}
add_action('template_redirect', 'disable_author_archives');
 
// Block REST API user enumeration for non-authenticated users
function disable_rest_user_enumeration($response, $user, $request) {
    if (!current_user_can('list_users')) {
        return new WP_Error(
            'rest_forbidden',
            'You do not have permission to access this resource.',
            array('status' => 403)
        );
    }
    return $response;
}
add_filter('rest_prepare_user', 'disable_rest_user_enumeration', 10, 3);

Security Headers

Add security headers via .htaccess or a plugin:

<IfModule mod_headers.c>
    # Prevent clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    
    # Prevent MIME type sniffing
    Header always set X-Content-Type-Options "nosniff"
    
    # Enable XSS filter
    Header always set X-XSS-Protection "1; mode=block"
    
    # Referrer policy
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    
    # Content Security Policy (customize based on your needs)
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
    
    # Strict Transport Security (only if using HTTPS)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

File Permissions

Set correct file permissions to prevent unauthorized modifications:

# Directories should be 755 or 750
find /path/to/wordpress/ -type d -exec chmod 755 {} \;
 
# Files should be 644 or 640
find /path/to/wordpress/ -type f -exec chmod 644 {} \;
 
# wp-config.php should be more restrictive
chmod 600 wp-config.php
 
# wp-content/uploads needs write access but no PHP execution
# Handle PHP execution blocking in .htaccess

Create .htaccess in wp-content/uploads:

# Prevent PHP execution in uploads directory
<Files *.php>
deny from all
</Files>

Monitoring and Logging

Enable logging to detect attacks:

// Log failed login attempts
function log_failed_login($username) {
    $log_file = WP_CONTENT_DIR . '/security-log.txt';
    $timestamp = current_time('mysql');
    $ip = $_SERVER['REMOTE_ADDR'];
    $user_agent = $_SERVER['HTTP_USER_AGENT'];
    
    $log_entry = sprintf(
        "[%s] Failed login for user '%s' from IP %s - UA: %s\n",
        $timestamp,
        sanitize_user($username),
        $ip,
        substr($user_agent, 0, 100)
    );
    
    file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
}
add_action('wp_login_failed', 'log_failed_login');

Why Traditional Pentesting Falls Short

WordPress security testing presents unique challenges. The platform's flexibility means every installation is different—different themes, different plugins, different configurations. Manual penetration testers must understand WordPress internals, identify which of thousands of possible plugins are installed, and test each component for vulnerabilities.

Traditional pentests are also point-in-time assessments. WordPress sites change frequently: plugins update, content changes, new plugins get installed. A vulnerability could be introduced a week after the pentest, leaving the site exposed until the next assessment.

Additionally, WordPress vulnerabilities are often discovered rapidly. New plugin vulnerabilities are disclosed weekly, and attackers quickly build exploits. Annual or quarterly pentests can't keep pace with this threat landscape.

How Agentic Testing Secures WordPress Sites

RedVeil's AI-powered penetration testing is designed to handle the complexity of WordPress security. RedVeil's agents autonomously discover your WordPress installation's components—core version, themes, plugins—and test each for known vulnerabilities and misconfigurations.

RedVeil doesn't just check for outdated versions. It tests authentication flows, probes for SQL injection and XSS in installed plugins, and identifies configuration weaknesses that could be exploited. When RedVeil finds a vulnerability, it validates the finding with proof-of-concept evidence, eliminating false positives.

With on-demand testing, you can assess your WordPress site's security after updates, plugin installations, or configuration changes. Each finding includes clear remediation guidance specific to WordPress, helping you quickly close security gaps.

Conclusion

WordPress security requires attention to core configuration, careful plugin management, and robust admin protection. The platform's popularity makes it a constant target, and default configurations leave many sites vulnerable.

Traditional security testing struggles to keep pace with WordPress's dynamic nature and the constant stream of plugin vulnerabilities. On-demand, agentic penetration testing from RedVeil provides the autonomous, intelligent assessment WordPress sites need to stay secure.

Start securing your WordPress site with RedVeil at https://app.redveil.ai/ and protect your site from the threats targeting WordPress installations daily.

Previous

No previous article

Next

Terraform Security Best Practices

Ready to run your own test?

Start your first RedVeil pentest in minutes.