Hardening WordPress After Finding My Own Site Wide Open

On 19 September 2026 I pointed curl at an endpoint on my own blog that I had never thought about, and it politely handed me back the username I log in with.

Nothing had been hacked. There was no malware, no defacement, no strange admin account. What I found was a set of defaults nobody had asked me about and one genuinely careless setting of my own, and the combination is the reason this post exists. Almost every WordPress security article is written as if hardening begins with installing something. Mine begins with a handful of requests that tell you what your site is currently telling strangers.

The endpoint that publishes your login

WordPress ships a REST API that is open to the public by default, and one of its collections lists the users who have published content. Try it on any WordPress site:

curl -s https://yoursite.com/wp-json/wp/v2/users | head -c 400

What comes back is a JSON array with each author’s display name and, more to the point, a slug. On a default installation that slug is the account’s login name, because WordPress derives the author archive URL from it. Mine was. So were the author archives at /?author=1, and so was the entry in the users section of the sitemap that WordPress generates automatically — three separate doors onto the same piece of information, none of which I had deliberately opened.

Why it matters is simple arithmetic. A login attempt needs two things. If the username is published, every automated attempt against your site starts with one of them already solved, and the whole defence rests on the password. This is not a vulnerability and I am not going to dress it up as one — it is documented behaviour that exists because author archives are a feature. But there is no reason for the login name and the public name to be the same string, and on a one-author blog there is no reason for the endpoint to answer anonymous requests at all.

What I am putting in place is a must-use plugin that unregisters the public users collection, strips the author URL out of the oEmbed response, drops the users section from the sitemap, and redirects the old author archive to a real, hand-written author page. A must-use plugin rather than the theme’s functions file, so a theme update cannot silently revert it. The check afterwards is the same request as above: it should answer 404 with rest_no_route, and /?author=1 should redirect rather than render.

A comment system nobody could use, collecting spam anyway

The second finding was entirely my own doing. Comments were enabled site-wide on a theme that renders no comment form. No reader had ever been able to leave one. The only things arriving were automated, and they were arriving into a queue I never looked at.

That is worse than it sounds, because the form is not the only way in. The comment endpoint accepts submissions directly, and trackbacks and pingbacks are separate mechanisms again. A comment setting you cannot see in the page is still a setting; it is just a setting you have stopped auditing. I am turning comments and pingbacks off, in the discussion settings and on every existing post. If I ever want a comment section I will add one deliberately, with moderation, rather than leaving an unattended intake open on the theory that nothing can reach it.

The broader lesson, which applies well beyond comments: every feature you are not using is a surface you are not watching. Deactivated plugins still sit in the filesystem. Unused user accounts still authenticate. An old staging copy in a subdirectory is a second, unpatched website wearing your domain name.

What the response headers admitted in one command

The next five seconds of this audit are free, and they tell you more about your hosting than any dashboard:

curl -sI https://yoursite.com/ | sort

Running that against this blog on 20 September 2026 returned Server: LiteSpeed, X-Powered-By: PHP/8.3.33, a Content-Security-Policy: upgrade-insecure-requests header, and a Link header advertising the REST API root. The PHP version is genuinely good news — a supported branch rather than something abandoned three years ago — and I would rather it were not announced to everyone, though anyone determined can fingerprint it anyway. The content-security-policy line is the host’s default, not something I configured; it upgrades insecure subresource requests and does nothing else. It is not a content security policy in the sense people mean when they recommend one.

More interesting is what did not come back. I ran the same check across all five websites I own that afternoon — two WordPress blogs on shared hosting, a static site on Cloudflare Pages, a small business site behind the host’s CDN, and an application on my own VPS. Not one of them sends Strict-Transport-Security. None. Including the one where I control the whole server and have no excuse.

HSTS is the header that tells a browser to refuse plain HTTP for your domain for a stated period, which closes the gap where a first visit to http:// can be intercepted before your redirect fires. It is not something hosting gives you; it is a line you add, in .htaccess on a LiteSpeed or Apache shared plan, in the server configuration elsewhere. It is also the one item on this list with teeth: get the max-age wrong, or include subdomains you have not checked, and you have locked browsers out of part of your own estate for months. Start with a short max-age, confirm nothing breaks, then raise it. Preloading is a further commitment and getting removed from the preload list is slow.

The certificate was the one thing I did not have to fix

This page used to be an article recommending SSL certificate providers. It was nonsense, and here is what replaced it: I have never bought a certificate, and on the evidence of my own sites I cannot think of a reason a blog would.

Checked on 20 September 2026, four of my five sites carry free Let’s Encrypt certificates — expiring 6 December, 26 October, 30 October and 21 November 2026 respectively — and the fifth carries a Google Trust Services certificate, which is what Cloudflare issues, valid to 2 November 2026. Every one of them renews without my involvement on the platforms that manage it. Read it with one line:

echo | openssl s_client -servername yoursite.com -connect yoursite.com:443 2>/dev/null \
  | openssl x509 -noout -subject -issuer -enddate

Paid certificates differ from free ones in what the issuer verifies about the organisation behind the domain, not in the cryptography protecting the connection. If your procurement process requires an organisation-validated certificate, buy one; that is a paperwork requirement, not a security upgrade. I have no prices to quote here because I have not bought one and did not check those pages on 20 September, and I would rather leave the gap visible than fill it with figures I have not verified.

The one place certificates have genuinely cost me time is the machine where I am the administrator. Automatic certificates are invisible until they are not, and on my own VPS that happened two days after launch, when an iPhone refused a certificate that every other check had accepted — the managed-versus-unmanaged side of that is in the post on what managed hosting actually does for me and what I have to do myself. On shared hosting this is somebody else’s problem, and that is worth something.

The list of people who can prove they own your site

Here is the item that almost nobody audits: the verified owners of your Search Console property. Verification survives things people assume clear it out. Removing a plugin, restoring a backup or changing every password in the account does not necessarily remove a verification token sitting in DNS, in a file in the web root or in a tag in the template.

So the check is: open users and permissions on every property you have, look at the verified owners list, and confirm that each entry is someone you would name out loud. If there is one you did not add, remove it, then find the token that proved them — the DNS record, the HTML file, the meta tag — and remove that too, because the entry and the token are two different things. Where those screens are and what else lives in them is in the guide to the parts of Search Console that actually matter.

What this post is not going to claim

I would rather be explicit about the boundaries than let the omissions look like modesty.

  • I have not been hacked, so there is no incident story here and no cleanup walkthrough. I am not going to write one from research and present it as experience.
  • I have not tested security plugins against each other. Comparing firewalls or malware scanners requires attacking something on purpose and measuring what gets through. I have not done that, so any ranking from me would be a summary of their feature pages. The plugins I actually run, and the one I removed and why, are in the post on the plugins I keep.
  • No percentages. You will have read that some share of WordPress breaches come from outdated plugins. I have no dataset, so I am not repeating a number I cannot source.
  • No support anecdotes. I have not opened a security ticket with any host, so I cannot tell you how any of them respond when something goes wrong.

Risk removed per minute, highest first

Ordered by how much it reduces risk per minute spent, which is not the order these lists usually appear in:

  1. Make sure a restore exists and works. Everything below assumes you can go back. My own routine, including what each copy does and does not cover, is in the post on the backup routine I wrote down after nearly needing it.
  2. Run the three requests from this post against your own site: the users endpoint, the headers, the certificate. Ten minutes, and you learn what you are publishing.
  3. Separate the login name from the public name, and close the author endpoints if you do not use author archives.
  4. Unique password plus two-factor on the admin account, and one account per human with the smallest role that works. Two-factor is the single control that makes username exposure survivable.
  5. Update core, themes and plugins, then delete everything deactivated. Dormant code is still code on disk.
  6. Turn off what you are not using — comments, pingbacks, dashboard file editing, XML-RPC if nothing depends on it.
  7. Audit the owners of your Search Console property and your hosting account, including anyone you gave access to once.
  8. Then add HSTS, carefully, with a short max-age first.

What strikes me looking back at that afternoon is that nothing I found required any access. The username, the headers, the certificate, the open comment intake — all of it was visible to anyone with curl and thirty seconds, and none of it appeared anywhere in the admin dashboard as a warning. The panel was perfectly happy. The site was serving pages. The only way to find any of it was to stop asking the dashboard what it believed and start asking the server what it was actually sending. The background on the account and the platform these sites sit on is in my review of the Hostinger account these sites run on.