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.
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=1scans 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.



