Security and ethical hacking

Hardening WordPress, in the order that matters

WordPress security is six jobs, and their order matters more than the jobs do: keeping everything updated, least privilege on every account, two factor authentication, a backup you have restored once, a security plugin that is still maintained, and hardening the login page. If you only ever do the first one, you have prevented more than any other combination of the remaining five.

  • Lesson 7 of 12
  • Intermediate
  • Free, no signup

What counts as hardening and what does not

WordPress hardening audit sheetSix steps

This counts as hardened

  • Core, theme and every plugin are current, and a separate vulnerability scanner also looks at them
  • You have read the administrator list and every account that need not be one is not
  • Two factor is on with an authenticator app, and you have also looked at that account application passwords
  • You restored the latest backup into a temporary folder and opened it
  • A copy of the backup also lives off this server

This is a feeling, not security

  • Changing the login page address, on its own
  • Hiding the WordPress version number
  • Changing the database table prefix on a live site
  • Turning the REST API off entirely
  • A long list of firewall rules that never gets read

This sheet is about WordPress only. The server it sits on, the email account password resets go to, and the computer you log in from each have a sheet of their own.

Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.

The real order, and why most checklists have the wrong one

A WordPress security checklist usually has thirty items and they all look the same size. In practice they are not. The first three prevent more than the remaining twenty seven, and the order is the thing nobody writes down, because a long list looks more professional.

The order we work through on a client site has six steps, and it is not sorted by ease. It is sorted by how much each one prevents.

Updates first, because most successful break ins to a WordPress site come through a public, already patched vulnerability. Then least privilege, because the size of the damage is set by what the accounts that got in were allowed to do. Then two factor authentication, which turns a leaked password from a disaster into a warning. Then a backup you have restored once, because an untested backup is an assumption, not a copy. Then a security plugin that is still maintained. And last of all, hardening the login page, which is useful and belongs at the end of the list rather than at the front.

The first three steps are entirely in your own hands and none of them costs money. The fourth costs a little and buys the most peace. The fifth and sixth are the ones people usually do first, and they have the smallest effect of the six.

Six glass steps in strict order with the first red and largest, and a single key on a single hook

Six steps, in the order of what each one prevents

  1. Update everything

    Most successful break ins come through a public, already patched vulnerability.

    1
  2. Least privilege on every account

    The size of the damage is set by what the compromised account was allowed to do.

    2
  3. Two factor authentication

    Turns a leaked password from a disaster into a warning.

    3
  4. A backup you have restored once

    An untested backup is an assumption, not a copy.

    4
  5. A security plugin that is still maintained

    An abandoned security plugin is itself an unupdated plugin.

    5
  6. Hardening the login page

    Useful, and its place is the end of the list rather than the front.

    6

This order is for an ordinary WordPress site. One that takes online payments or holds sensitive user data has further steps that this lesson does not cover.

Updates, and why this one prevents more than the rest

When a vulnerability is published, two things are born at the same moment: the description of the problem and the patch for it. From that moment on, anyone who wants to use it knows exactly where to aim, and anyone who has updated is off the list. The whole game is the gap between publication and update.

On this site, from March 2025 until today, the vulnerability scanner has recorded 13 advisories for software installed on this one installation. 10 of them were in plugins, 2 in WordPress core itself and 1 in a theme. By type, seven were cross site scripting, two were broken access control, and the rest were one each: arbitrary code execution, sensitive data exposure, arbitrary file upload and PHP object injection.

Twelve of those thirteen were closed by an update, most of them within days. That number is the one worth looking at: in eighteen months, on a site where nothing dramatic happened, software running on it was declared vulnerable thirteen times and nearly every time the answer was one button.

And now the part that gets said less, and that we ran into ourselves: an update counter reading zero is not proof of being up to date. Commercial plugins update through their own channel, and a lapsed licence makes that counter quiet rather than current. On this installation the counter reads zero and the vulnerability scanner disagrees with it about one item. That is why a vulnerability scanner is needed alongside the update counter; the second sees your installation, the first sees the published list.

We wrote up one recent core example separately: the WordPress 7.0.4 security patch and which servers were actually at risk.

Where those 13 advisories came from

March 2025 to September 2026, for software installed on this one site.

13advisories
  • Plugins 10 advisories
  • WordPress core 2 advisories
  • A theme 1 advisory

These thirteen are not a list of WordPress vulnerabilities; they are only the ones that matched software installed on this one site. Another site with other plugins gets another number.

Accounts, and why a site with 1,224 users has exactly one administrator

This site has 1,224 user accounts. One of them is an administrator. The other 1,223 hold roles that cannot install anything, cannot edit a theme file, and cannot see any other user data.

That number is a decision rather than an accident, and the reason is simple: an account that gets compromised hands over exactly what that account could already do. Least privilege is not bureaucracy, it is the size of the blast.

There are two places this rule usually breaks, and both come from good intentions. First, giving an administrator account to a developer or an agency for now, and never removing it; the administrator list on a five year old site usually holds people who stopped working on it three years ago. Second, roles a plugin has added. WordPress adds capabilities up, so a plugin that gives subscribers one new ability has changed your access list and written the news down nowhere.

The next step is two factor authentication, and which method you pick matters. On this site only the authenticator app method is enabled, neither SMS nor email. The reason is that SMS depends on a phone number and a number can be taken from an operator, and email depends on a mailbox that itself opens with a password; if that password is in the same leak, the second factor has stopped being a second factor.

And one point specific to WordPress: application passwords. These are separate passwords created to connect tools, and they go around two factor authentication by design, because they are meant to work without a person. If you have turned two factor on, look at the application password list on that same account as well; otherwise an open door is still standing next to the locked one.

An untested backup is not a backup, and it can leak too

A backup you have never restored is a folder. That sentence sounds like a cliche until the day you need it and find out the file was truncated, or that the password needed to open it lived inside the site that is gone.

But there is a second point that almost nobody makes, and we ran into it on this very server.

The common WordPress migration plugin builds two hundred megabyte archives, puts them in a folder under wp-content, and protects that folder with a .htaccess file. On nginx that file does nothing at all; it is never even read. Before we wrote the rule that is there now, we tested it and found the archive downloadable over plain HTTP, with the server even honouring a partial download request. Inside each archive is the entire database: the user rows, the password hashes, the service keys and the contents of the configuration file.

Which means a perfectly good backup, sitting in the wrong place, becomes the largest leak available. One file, the whole site.

The way this site handles it now is to hand a request for such a file to PHP for an authorisation check; a logged in administrator gets the file and everybody else gets a 404. We tested it again today: a guest request for one of those archives, even with a partial request header, came back 404.

Three rules survive that story. A backup should not stay on the same machine, because a backup next to the site survives a mistake and does not survive the machine. Access to the backup should be as restricted as access to the database itself. And practise a restore once a year into a temporary folder; how to do that is covered separately in the computer basics track.

What is not worth your time

Now the position, plainly, because a large part of WordPress security advice consists of things that give a feeling of security without giving security.

Obscurity on its own is not a defence. Changing the address of the login page, hiding the WordPress version number and changing the table prefix close no vulnerability at all; they only slow an automated scanner down a little. Anyone who is targeting you specifically will find all three within minutes.

And now the honest half, because this site does all three of those things. The login address here has been changed and the plugin file paths have been rewritten as well. What that buys is less noise, not more security, and our own log shows both halves: in three days, 127 requests went to the default WordPress login address and not one of them reached a login form, because that address redirects a guest to another page. Those 127 requests were not removed; they simply no longer land on anything. If this same site were not kept updated, that changed address would not last a day.

Three more things that get recommended and that we do not recommend. Changing the database table prefix on a live site carries more risk than benefit; it half completes, and plugins that stored the prefix somewhere break. Turning the REST API off entirely breaks the WordPress editor and solves nothing that restricting access does not solve. And a security plugin whose contribution is a long list of firewall rules has added nothing if you never read the list.

There is a simple test you can put on any security recommendation: which specific attack does this stop, and what does it change if someone is already inside. Advice that has no clear answer to those two questions is usually a feeling.

The fast path, with AI

The ordinary way to audit WordPress security is to open a thirty item checklist and hunt for each item in the dashboard. The fast way is to have WordPress itself build the list for this particular installation and let the model only rank it. The difference is not that it is quicker; it is that the output is about your site rather than about a site in general. A stronger model does better here than a cheap one, because this step is judgement about order; our current pick is listed in this site AI section.

  1. Run four WP-CLI commands. The four outputs together are exactly the list a security audit would build.
  2. Hand the output to the model and ask for a work list, ranked by effect rather than by ease.
  3. Tell the model that the update column lies for commercial plugins. Without that sentence it will mark those rows as current.
  4. Tick the two items the model cannot see yourself: that the backup actually restores, and that it also exists somewhere else.

Copy-ready recipe

Four commands, on the site itself:

  wp plugin list --fields=name,status,version,update
  wp theme list --fields=name,status,version,update
  wp user list --role=administrator --fields=user_login,user_registered
  wp core verify-checksums

The third command deliberately has no email column, so the output holds no
personal data.

The prompt, with all four outputs pasted underneath it:

  This is the output of four WP-CLI commands on a WordPress site. Build a work
  list, ranked by what each item prevents rather than by how easy it is to do.
  For each item write: a) what to do, b) why it sits where it sits, c) what
  stays open if it is skipped.
  Follow these four rules:
  1) The update column lies for commercial plugins. Mark any plugin that is
     not in the public WordPress repository as unclear, not as current.
  2) A deactivated plugin still has to be updated; its files are on disk.
  3) If there is more than one administrator account, ask separately for each
     one why it is an administrator.
  4) Do not give an opinion on whether the site is secure; this data cannot
     answer that.

Before you trust the output: Two of this lesson six steps do not appear in the output of these commands at all, and they happen to be the two most often skipped: that the backup actually restores, and that a copy of it lives off this server. The model cannot see either, and if you do not ask, it will pass over them unwritten. A third item is honestly outside the model reach too: the output of wp core verify-checksums has no checksums at all for bespoke and commercial plugins, so they come back as skipped, which is neither a confirmation nor a denial. Those rows you have to settle with their makers yourself.

AI in this kind of work

On WordPress security a language model is a good audit assistant and a bad configuration adviser. It is good at reading output: the plugin list, the user list, the text of an advisory and whether that advisory matches your version. It is bad at producing configuration, and the reason is that WordPress configuration depends on the server and the model has not seen your server.

Tools that actually help

  • Claude For ranking audit output by effect, and for reasoning about roles and capabilities, because it writes out its reasoning and reasoning can be argued with. Iran is on neither of Anthropic two supported-countries lists and there is no official payment route from Iran.
  • Gemini For volume work: reading several advisories one after another and pulling out which version range each one covers. Google own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.
  • WP-CLI Not an AI tool, and deliberately here. The real inventory of your installation comes from this; the model only reads what this produced and without it has nothing to read.
  • RGB The access and payment layer for Iran, kept separate from the tools themselves.

Where it backfires

Two risks here are specific to this topic. First, do not ask a model to make my site secure; what comes back is half remembered wp-config snippets and .htaccess rules, and on a live site they either do nothing at all, because the server is nginx and that file is never read, or they take the site down. Take configuration from WordPress own documentation and ask the model to explain it, not to produce it. Second and more serious: never paste wp-config into a general purpose tool. It holds the database password and the eight authentication keys, and those eight keys are what sign the session cookie of every logged in user; losing them means someone can sit in anybody seat without a password. Whether pasted content feeds model training depends on the plan and settings of that service, and the companies write it down, including on Google privacy page for the Gemini apps.

Sources: Google: Gemini Apps privacy Where you can use the Gemini web app Anthropic supported countries

Where this advice stops

None of these six steps survives an infected computer. If the laptop you log in from carries malware, you complete the two factor prompt yourself, by hand, and the session cookie is taken afterwards; as far as the site is concerned an ordinary administrator logged in and everything is fine. That is why the security of the machine you log in from is part of the security of the site, and it is not covered here. Two more boundaries, stated plainly: this lesson is about WordPress and not about the server underneath it, the hosting panel, or the email account password resets go to; and every number in it came from one installation, so the order of the steps generalises and the figures do not.

From our own work

Every number in this lesson was read off this installation on 7 September 2026. Thirteen vulnerability advisories from March 2025 until today, split ten to two to one between plugins, core and a theme, twelve of them closed by an update. One thousand two hundred and twenty four user accounts with exactly one administrator. Two factor authentication by authenticator app only, and login limits set at five attempts per address and ten per username inside a five minute window.
And two things we learned ourselves, both retold in this lesson. First, the backup archive: before the current rule was written, the .wpress file was downloadable over plain HTTP with an ordinary request, and today that same request from a guest, even with a partial download header, comes back 404. Second, the one we did not expect: in the plugin list of this installation the update column is empty for every row, and at the same time the vulnerability scanner still shows one open item. We deliberately did not write the name and version of that item; naming the exact location of an unpatched vulnerability on a live site is the thing this lesson tells you not to do.

Real follow-up questions

Is changing the dashboard password on its own enough?

For a password that might be in a leak, yes, it is necessary and it should be unique. But a new password does nothing about a vulnerability inside the code of a plugin; that way in does not pass through the login page at all. If you only do one thing, choose the update rather than the password, and if you do two, do both.

Is a free security plugin enough, or do I need a paid one?

The free tier of most serious plugins includes login rate limiting and event logging, and those two carry a large share of the benefit. What is usually paid for is vulnerability scanning and automatic alerts about your specific plugins, and after this lesson you know why that part matters. If you would rather hand the job to someone, that review and hardening is what we do under website security.

Should I turn automatic updates on?

For core security updates yes, without hesitation; WordPress has that on by default and there is no good reason to turn it off. For plugins it depends: on a site with a daily automatic backup and someone who looks at it once a week, turning it on pays. On a site with no backup, one automatic update can break the front page at midnight and nobody finds out until morning. Which means the answer to this question actually depends on step four of this same list, not on itself.