Secure Self‑Hosted WordPress: Harden Your Site in 6 Steps
Learn how to lock down your self‑hosted WordPress with application passwords, XML‑RPC disabled, rate limiting, and a WAF — all in under an hour.
Before you start: versions, package names and storage identifiers change over time. Check the commands against the official documentation for your setup before running them, especially anything that creates, deletes or overwrites data.
Most WordPress compromises are not clever. They are an out-of-date plugin, a reused admin password, or a writable directory that should not have been. This guide covers six changes that close those paths, in the order that gives the most protection per minute spent.
1. Update everything, and keep it that way
This is first because it prevents more incidents than everything below combined. WordPress’s own position: “you should always keep up to date with the latest version of WordPress. Older versions of WordPress are not maintained with security updates.”
The reason it matters so much is timing. When a vulnerability is disclosed, the information needed to exploit it is almost certainly already public — you are not racing a researcher, you are racing automated scanners that have the advisory too.
wp core update
wp plugin update --all
wp theme update --all
Then remove what you are not using. A deactivated plugin still has files on disk, and files on disk can still be reachable. Delete rather than deactivate.
2. Fix file permissions
The baseline WordPress recommends:
find /path/to/wordpress -type d -exec chmod 755 {} \;
find /path/to/wordpress -type f -exec chmod 644 {} \;
Directories 755, files 644. Then the scheme by directory:
| Path | Should be writable by |
|---|---|
Root / |
Your user only (except .htaccess if WordPress must rewrite it) |
/wp-admin/, /wp-includes/ |
Your user only |
/wp-content/ |
Your user and the web server |
/wp-content/plugins/ |
Your user only |
/wp-content/themes/ |
Your user only, if the theme editor is disabled |
The principle: the web server process should be able to write only where WordPress genuinely needs to write — uploads — and nowhere it could be tricked into placing executable PHP.
Never use 777. It is the single most common “fix” in support threads and it makes a directory world-writable.
3. Protect wp-config.php
This file holds your database credentials and your salts. WordPress recommends permissions of 400 or 440 — readable only by you and the web server.
chmod 440 wp-config.php
On Apache, deny it directly:
<Files "wp-config.php">
Require all denied
</Files>
You will also see advice to move wp-config.php one level above the web root. WordPress’s documentation is notably lukewarm on this: some people assert the security benefit is minimal, and doing it incorrectly “may actually introduce serious vulnerabilities”. If you are not confident about what your server serves from where, the permissions and the deny rule are the safer win.
4. Disable the file editor
Add to wp-config.php:
define( 'DISALLOW_FILE_EDIT', true );
WordPress is blunt about why: the dashboard editor “allows code execution” and is “the first tool an attacker will use if able to login.”
This is the highest-value single line in the whole guide. It converts a stolen admin password from “run arbitrary PHP on the server” into “vandalise some content” — a bad day instead of a breach.
5. Use Application Passwords, not your login password
For REST API access, CLI tooling, or anything integrating with the site, create a dedicated Application Password under Users → Profile. Each one is revocable individually without changing your password or disturbing anything else.
Pair it with a dedicated user at the lowest role that works. An integration that only publishes posts should be an Author, not an Administrator — so that a leaked credential cannot install a plugin.
6. Reduce the login attack surface
Two measures that matter, in order:
- Rate-limit login attempts, at the server or via fail2ban. Brute-forcing is cheap; making it slow makes it uneconomic.
- Enable two-factor authentication for administrators. This defeats credential stuffing outright, which no amount of password policy does.
On XML-RPC: it is commonly recommended to disable it, and if nothing you run uses it, doing so removes an endpoint that historically enabled amplified brute-force attempts. Check first — the Jetpack plugin and some mobile publishing apps depend on it, and disabling it will break them silently.
On database privileges
You will find advice to strip DROP, ALTER and GRANT from your WordPress database user. Worth knowing that WordPress itself advises against it: major updates perform schema changes, and a user without those privileges can fail mid-upgrade.
The documentation’s alternative is more useful anyway — separate databases with separate users per installation, so one compromised site cannot read another’s data, plus regular whole-database backups.
Verify
# confirm the editor is actually disabled
wp config get DISALLOW_FILE_EDIT
# look for anything world-writable
find /path/to/wordpress -perm -o+w -ls
# confirm wp-config is not served
curl -I https://example.com/wp-config.php
That last command should not return the file. Anything other than a 403 or 404 means the deny rule is not doing its job.
What this does not cover
Hardening reduces the chance of compromise; it does not eliminate it. The things that decide how bad an incident is are backups you have actually restored from, and keeping the site on infrastructure you can rebuild. A WAF and rate limiting are worth having, but neither is a substitute for a tested restore.
Sources
- WordPress — Hardening WordPress (permissions scheme,
DISALLOW_FILE_EDIT, wp-config permissions, database privilege guidance) - WordPress REST API Handbook
- WordPress — Application Passwords
Related reading
Some links on this page may be affiliate links. If you buy through them we may earn a commission at no extra cost to you. See our affiliate disclosure.