Web Security

WordPress Security Hardening: 12 Steps to Secure Your Site

Harden WordPress with wp-config.php settings, file permissions, disable XML-RPC, security headers, wpscan, Wordfence, and database table prefix changes.

March 9, 20266 min readShipSafer Team

WordPress Security Hardening: 12 Steps to Secure Your Site

WordPress powers over 40% of the web and is the most attacked CMS platform on the internet. The default installation is functional, not secure. Most WordPress compromises exploit known vulnerabilities in plugins, themes, or weak configurations that could have been prevented. This guide covers 12 concrete steps to harden a WordPress site against the most common attack vectors.

Step 1: Harden wp-config.php

wp-config.php is the most sensitive file in a WordPress installation. It contains database credentials and security keys.

Move it one directory above the web root so it cannot be accessed via HTTP:

# Move wp-config.php above public_html
mv /var/www/html/wp-config.php /var/www/wp-config.php

WordPress automatically looks one level up for this file.

Add security keys and salts (regenerate at https://api.wordpress.org/secret-key/1.1/salt/):

define('AUTH_KEY',         'unique-phrase-here');
define('SECURE_AUTH_KEY',  'unique-phrase-here');
define('LOGGED_IN_KEY',    'unique-phrase-here');
define('NONCE_KEY',        'unique-phrase-here');
define('AUTH_SALT',        'unique-phrase-here');
define('SECURE_AUTH_SALT', 'unique-phrase-here');
define('LOGGED_IN_SALT',   'unique-phrase-here');
define('NONCE_SALT',       'unique-phrase-here');

Disable file editing from the admin dashboard:

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);  // Also disables plugin/theme installation

Force SSL for admin:

define('FORCE_SSL_ADMIN', true);

Step 2: Set Correct File Permissions

Incorrect file permissions are a major attack vector. Set them with:

# Directories: 755 (owner read/write/execute, group+others read/execute)
find /var/www/html -type d -exec chmod 755 {} \;

# Files: 644 (owner read/write, group+others read)
find /var/www/html -type f -exec chmod 644 {} \;

# wp-config.php: 600 (only owner can read/write)
chmod 600 /var/www/html/wp-config.php

# Uploads directory: 755 (writable by web server)
chmod 755 /var/www/html/wp-content/uploads

Never set files or directories to 777. This allows any process on the server to write to them.

Step 3: Change the Database Table Prefix

The default WordPress database prefix is wp_. Automated SQL injection tools target this prefix specifically. Change it during installation or with a plugin like "Change DB Prefix" or WP-CLI:

// wp-config.php — set before initial installation
$table_prefix = 'xk7z_';  // Use a random string

If the site is already installed, use WP-CLI:

wp search-replace 'wp_' 'xk7z_' --all-tables

Then update the prefix in wp-config.php and the user_roles option:

UPDATE xk7z_options SET option_name = 'xk7z_user_roles'
WHERE option_name = 'wp_user_roles';

Step 4: Disable XML-RPC

XML-RPC is a remote API that WordPress uses for mobile apps and pingbacks. It is also used for:

  • Brute-force amplification attacks (one request can test hundreds of passwords)
  • DDoS amplification via pingbacks

Unless you specifically need it (Jetpack, mobile WordPress app), disable it:

# nginx — block XML-RPC
location = /xmlrpc.php {
    deny all;
    return 444;
}
# .htaccess — Apache
<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
</Files>

Or add to functions.php:

add_filter('xmlrpc_enabled', '__return_false');

Step 5: Add Security Headers

WordPress does not add security headers by default. Add them in nginx or .htaccess:

# nginx
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=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;

Note: unsafe-inline and unsafe-eval are unfortunately often required for WordPress plugins. Tighten this as much as your installed plugins allow.

Step 6: Protect the WordPress Admin Directory

Restrict access to /wp-admin/ by IP address:

# nginx
location /wp-admin/ {
    allow 192.0.2.0/24;    # Your office IP range
    allow 198.51.100.5;    # Your home IP
    deny all;
}

Add HTTP Basic Authentication as a second factor:

# Generate the credentials file
htpasswd -c /etc/nginx/.htpasswd adminuser
location /wp-admin/ {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    # Add IP allowlist here too
}

Step 7: Limit Login Attempts

WordPress allows unlimited login attempts by default, making brute-force attacks trivial. Install Login LockDown or Limit Login Attempts Reloaded, or handle it at the nginx level:

# nginx — rate limit wp-login.php
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m;

location = /wp-login.php {
    limit_req zone=wp_login burst=3 nodelay;
    include fastcgi_params;
    fastcgi_pass php-fpm;
}

Step 8: Use Wordfence or Equivalent

Wordfence is the most widely used WordPress security plugin. Its free tier includes:

  • Malware scanner (checks core files against official checksums)
  • Login security (2FA, rate limiting, IP blocking)
  • Firewall (blocks known bad IPs and attack patterns)
  • File change detection

Configure Wordfence to:

  • Enable 2FA for all admin and editor accounts
  • Set alert email threshold
  • Schedule weekly full scans
  • Enable "Live Traffic" to spot attacks in real time

Alternative: Sucuri Security (better for post-compromise cleanup and WAF).

Step 9: Run WPScan for Vulnerability Detection

WPScan is a black-box WordPress vulnerability scanner:

# Install
gem install wpscan

# Basic scan
wpscan --url https://yoursite.com

# Enumerate users (useful to know if your usernames are public)
wpscan --url https://yoursite.com --enumerate u

# Check plugins for known vulnerabilities (requires API token)
wpscan --url https://yoursite.com --enumerate vp --api-token YOUR_TOKEN

Run WPScan before deploying and after adding new plugins. Free API tokens are available at wpscan.com.

Step 10: Disable Directory Browsing

Without an index.php or index.html, web servers may list directory contents. Block this:

# nginx — disable autoindex
autoindex off;
# .htaccess — Apache
Options -Indexes

Step 11: Keep Everything Updated

Unpatched plugins account for the majority of WordPress compromises. Enable automatic updates:

// wp-config.php
define('WP_AUTO_UPDATE_CORE', true);  // Core minor updates

// functions.php — auto-update plugins and themes
add_filter('auto_update_plugin', '__return_true');
add_filter('auto_update_theme', '__return_true');

Use the WP Vulnerability Checker plugin or integrate with the WPScan API to get alerts when a plugin you use has a known CVE.

Step 12: Regular Backups Off-Site

A good backup strategy is your recovery plan when hardening fails:

# WP-CLI backup
wp db export backup-$(date +%Y%m%d).sql --add-drop-table
tar -czf wp-content-backup-$(date +%Y%m%d).tar.gz wp-content/

# Send to S3
aws s3 cp backup-$(date +%Y%m%d).sql s3://your-backup-bucket/db/

Use UpdraftPlus or BackWPup to automate daily backups to S3, Google Drive, or Dropbox. Test restoration quarterly.

Summary Checklist

  • wp-config.php moved above web root and permissions set to 600
  • Security keys and salts regenerated
  • DISALLOW_FILE_EDIT and FORCE_SSL_ADMIN set
  • Database prefix changed from wp_
  • XML-RPC disabled (unless required)
  • Security headers added at web server level
  • /wp-admin/ protected by IP allowlist or Basic Auth
  • Login rate limiting enabled
  • Wordfence or Sucuri installed and configured
  • WPScan run and findings remediated
  • Directory browsing disabled
  • Auto-updates enabled, backups scheduled and tested

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.