Most WordPress compromises are not exotic zero-days. They are brute-forced logins, outdated plugins, loose file permissions and default settings nobody changed. A security plugin can help, but everything that actually matters can also be done with configuration files, server rules and core settings. Pairing these practices with a clean custom WordPress theme built from scratch minimizes third-party vulnerability vectors. This guide walks through the full hardening pass, step by step, with no plugin installed.

Step 1 — Updates are the foundation

The single highest-value security habit is updating: WordPress core, themes and plugins. Enable automatic updates for everything in wp-config.php:

define( 'WP_AUTO_UPDATE_CORE', true );
            add_filter( 'auto_update_plugin', '__return_true' );
            add_filter( 'auto_update_theme', '__return_true' );

(The two filters go in a child theme’s functions.php or a small must-use plugin you write yourself.) Then delete — not deactivate, delete — every plugin and theme you do not use. Inactive code still sits on the server and can still be exploited.

Step 2 — Lock down the login page

Brute-force bots hammer wp-login.php around the clock. Two server-level moves stop most of it. First, put the admin area behind HTTP basic authentication at the server level (Apache example, in an .htaccess file inside wp-admin, plus a password file created with htpasswd). Second, block XML-RPC, the legacy endpoint bots use to try hundreds of passwords per request. Add this to your main .htaccess:

# Block XML-RPC entirely
            <Files xmlrpc.php>
              Order Deny,Allow
              Deny from all
            </Files>

Also rename nothing and hide nothing as your main defence — obscurity is a speed bump, not a wall. The wall is: a unique username (never “admin”), a long generated password, and two-factor authentication on every administrator account.

Step 3 — Turn off the dangerous conveniences

WordPress ships with a built-in theme and plugin editor in the dashboard. If an attacker gets into an admin account, that editor is a direct path to executing PHP. Disable it, and disable unfiltered uploads and installer behaviour while you are in wp-config.php:

define( 'DISALLOW_FILE_EDIT', true );  // disables the dashboard theme/plugin editor
define( 'FORCE_SSL_ADMIN', true );     // forces HTTPS for all admin sessions

// Choose one of the two below:
// define( 'DISALLOW_FILE_MODS', false ); // default — updates & installs work normally
define( 'DISALLOW_FILE_MODS', true );    // fully locked-down production: no dashboard updates

Set DISALLOW_FILE_MODS to true only if the site is maintained by other means (SSH, CI pipeline) — it also blocks plugin and theme installs from the dashboard. Leave it false if you still manage updates through the WordPress admin.

Step 4 — Fix file permissions

Permissions should follow one rule: files readable by the web server, writable only where uploads and updates genuinely need it. The standard scheme:

# Directories: 755, files: 644
            find /var/www/html -type d -exec chmod 755 {} \;
            find /var/www/html -type f -exec chmod 644 {} \;
            chmod 600 wp-config.php

wp-config.php gets the strictest treatment — it holds your database credentials. On many hosts you can also move it one directory above the web root; WordPress finds it there automatically, and it is no longer reachable over HTTP at all.

A padlock chained to a laptop keyboard, representing locked-down WordPress security

Step 5 — Add security headers

Headers tell browsers how to behave around your site. These Apache rules (Nginx uses add_header equivalents) cover the important ones:

<IfModule mod_headers.c>
              Header set X-Content-Type-Options "nosniff"
              Header set X-Frame-Options "SAMEORIGIN"
              Header set Referrer-Policy "strict-origin-when-cross-origin"
              Header set Permissions-Policy "geolocation=(), camera=(), microphone=()"
            </IfModule>

Step 6 — Shrink the attack surface

  • Hide the version number. Remove the generator meta tag: remove_action('wp_head', 'wp_generator');

  • Disable author enumeration. Block ?author=1 scans with a rewrite rule so usernames cannot be harvested.

  • Use a custom database prefix. Anything other than wp_, set at install time — changing it later needs a careful migration.

  • Limit login attempts at the server. Tools like Fail2ban read the web server log and ban IPs after repeated failed logins — no WordPress plugin involved. (If you decouple your stack completely, explore how to build a headless WordPress site with Next.js to isolate the admin panel from public web traffic).

Step 7 — Backups you have actually tested

Hardening reduces risk; backups decide whether an incident is an annoyance or a disaster. Automate a full backup (files + database) to storage that is not on the same server, keep at least the last 14 daily copies, and — the step everyone skips — restore one to a staging site once, so you know the process works before you need it under pressure.

Security is not a plugin, a setting or a scan. It is the accumulation of boring defaults chosen correctly: updates on, editor off, permissions tight, logins strong, backups tested.