ThemesCorners
Blog
10 min readby ThemesCorners

WordPress Security Hardening Checklist for 2026

A no-fluff list of the dozen settings that actually move the needle on WordPress security this year — including the ones added in 6.7 and 6.8.

The WordPress attack surface in 2026 is different from 2022. Most exploits we see today are not zero-days in core — they're stolen application passwords, plugin RCE chains that abused REST endpoints, and supply-chain takeovers of small plugins. This list is the dozen things we actually do on every site we ship.

1. Force application passwords for the REST API

WordPress 6.7 made application passwords the recommended way to authenticate machine traffic. Disable password-based REST authentication for non-cookie clients:

add_filter('wp_is_application_passwords_available', '__return_true');
add_filter('rest_authentication_errors', function ($result) {
    if (! empty($result)) return $result;
    if (! is_user_logged_in() && ! wp_get_current_user()->ID) {
        return new WP_Error('rest_not_logged_in', 'Authentication required.', ['status' => 401]);
    }
    return $result;
});

Combine with rate-limiting at the CDN layer — Cloudflare's free tier is enough.

2. Enable the new auto-update integrity checks

WordPress 6.8 ships signed core updates by default (Ed25519). For plugins, opt in:

add_filter('automatic_updates_is_vcs_checkout', '__return_false');
add_filter('auto_update_plugin', '__return_true');

Then add a notification webhook so you know within minutes if an update fails verification:

add_action('automatic_updates_complete', function ($update_results) {
    if (! empty($update_results['plugin']['failed'])) {
        wp_remote_post('https://hooks.slack.com/...', [
            'body' => json_encode(['text' => 'WP auto-update failed']),
        ]);
    }
});

3. Disable XML-RPC unless you really use it

Most sites don't. It's the second-most-attacked endpoint after /wp-login.php:

add_filter('xmlrpc_enabled', '__return_false');

Confirm you don't have Jetpack publicize, the WP mobile app, or a legacy IFTTT integration first.

4. Move /wp-admin behind HTTP basic auth

For sites with a fixed editor team, add Nginx-level basic auth in front of the admin:

location ^~ /wp-admin/ {
    auth_basic           "Admins only";
    auth_basic_user_file /etc/nginx/.htpasswd-tc;
    try_files $uri $uri/ /index.php?$args;
}

Layered authentication means even a stolen WordPress password is useless without the basic-auth credentials, which never travel over the public auth API.

5. Pin plugin checksums

WordPress 6.7 added wp plugin verify-checksums to WP-CLI. Run it nightly via cron and alert on any mismatch — that's how you catch a supply-chain compromise in hours instead of weeks.

6. Replace wp_die() with a custom error page

Default wp_die() reveals the WordPress version on every fatal error. A simple custom handler:

add_filter('wp_php_error_message', function () { return ''; });
add_filter('wp_php_error_args', function ($args) {
    $args['response'] = 500;
    return $args;
});

7. Force-rotate session keys on suspicion

Add a one-click admin button that runs:

wp_destroy_other_sessions();
\WP_Session_Tokens::get_instance(wp_get_current_user()->ID)->destroy_all();

If you suspect a stolen cookie, this immediately invalidates every active session for every user.

8. Disable file editing from the dashboard

This one is so basic it's embarrassing how often we still find it missing:

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);  // also blocks plugin installs from dashboard

The second constant prevents anyone — even a compromised admin — from installing a malicious plugin through the UI.

9. Set strict Content-Security-Policy headers

In your edge config (Vercel, Cloudflare Workers, Nginx):

Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-...';
  style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
  frame-ancestors 'none'; base-uri 'self';

'unsafe-inline' for styles is the price of using the block editor — but frame-ancestors 'none' blocks clickjacking and base-uri 'self' blocks <base> injection, two real attack vectors we've seen in 2025.

10. Use Sentry (or equivalent) for PHP errors

Every PHP warning matters. Stack-trace patterns are how attackers find exploitable code paths. Get all warnings to one place; an unexplained Undefined index in a query parameter is often an attacker probing.

11. Enable 2FA — but use passkeys, not TOTP

WordPress 6.7 added native passkey support via the WebAuthn API. Force it for all editors:

add_filter('manage_users_columns', function ($cols) {
    $cols['passkey'] = 'Passkey';
    return $cols;
});
add_action('user_register', function ($userId) {
    update_user_meta($userId, '_passkey_required', 1);
});

Passkeys aren't phishable. TOTP is.

12. Run wp doctor weekly

The WP-CLI doctor command from version 2.10 onwards now ships a built-in security suite. Pipe its output to your monitoring:

wp doctor check security --format=json | jq -e '[.[]|select(.status!="success")]|length==0'

Exit code 1 = something to fix this week.

Stop doing these things

  • Renaming /wp-admin or /wp-login.php. Security through obscurity. Wastes 30 minutes setup, breaks plugins, blocks zero real attackers.
  • Installing 7 security plugins. They overlap, conflict, and tank performance. Wordfence or iThemes Security, one of them, configured correctly, is enough.
  • IP-allowlisting /wp-admin from your home IP. Your ISP rotates that IP and you lock yourself out at 11pm on a Friday. Trust us, we've been there.

A note on hosting

Managed WordPress hosting in 2026 (Pressable, Kinsta, WP Engine, Cloudways) handles 1–4 of this list for you transparently. If you're on raw VPS, you own all twelve. Budget accordingly.

Related articles