The Anatomy of a Security Hardening
A Real-World Case Study in Proactive WordPress Defense
Introduction: Before the Breach
Most security case studies begin with disaster. A compromised database. Stolen customer data. A frantic call from a client whose website is redirecting visitors to pharmaceutical spam.
This isn’t one of those stories.
This is what security work looks like when it’s done before something goes wrong—the quiet, methodical work of closing doors before anyone tries to open them. It’s less dramatic than incident response, but arguably more valuable. The best security incidents are the ones that never happen.
A professional services firm reached out through Upwork with a straightforward request: security hardening for their WordPress site. They were running a clean installation on premium managed hosting. No signs of compromise. No suspicious activity. They simply wanted their site locked down properly—and they explicitly didn’t want one of those bloated “security” plugins that promise to do everything and mostly just slow sites down.
What followed was a systematic assessment and hardening engagement that illustrates what thorough WordPress security actually looks like in practice. Not the theatrical security of warning badges and threat counters, but the structural security of proper configurations, minimal attack surface, and defense in depth.
Part One: The Assessment
Starting With What’s There
Before changing anything, you need to understand what you’re working with. Security hardening without assessment is just guessing—you might fix things that aren’t broken while missing actual vulnerabilities.
The client’s site ran WordPress 6.9 on Kinsta hosting, using the Divi theme. Good foundation. Kinsta provides enterprise-grade infrastructure with Cloudflare protection, automatic daily backups, and server-level security controls. Divi, while complex, was current.
The assessment covered several domains:
User Accounts — Four legitimate users: three administrators and one author. The administrator count was higher than ideal (most sites only need one or two), but all accounts were real team members with appropriate roles. No orphaned accounts, no suspicious users, no signs of unauthorized access.
Core Configuration — The wp-config.php file was mostly solid. Security salts were properly externalized. Debug mode was disabled. But two important constants were missing: DISALLOW_FILE_EDIT (which prevents editing theme and plugin files through the admin dashboard) and FORCE_SSL_ADMIN (which enforces HTTPS for all admin operations). Neither absence was critical—Kinsta enforces SSL at the server level—but defense in depth means not relying on a single layer.
Plugin Inventory — This is where most WordPress assessments get interesting. The site had accumulated the usual collection:
- Three inactive plugins that served no purpose and only expanded the attack surface
- A backup plugin redundant with Kinsta’s built-in backup system
- Migration tools left over from a past site move
- Hello Dolly (the placeholder plugin WordPress includes by default, which does nothing useful)
- Two plugins with configuration warnings suggesting they might not be actively used
- A handful of plugins that needed updates
Nothing malicious. Nothing obviously vulnerable. Just the kind of cruft that accumulates on any WordPress site that’s been around for a while.
Filesystem — A sweep of the web root revealed standard provisioning leftovers: a googlecloud.html file from Kinsta’s setup process, an empty index.html placeholder, and the readme.html file that ships with WordPress (which discloses version information to anyone who knows to look for it). The .htaccess file contained orphaned rules from a caching plugin that had been removed—harmless, but sloppy.
Attack Surface — XML-RPC, the legacy remote procedure call interface, was present. Kinsta was already blocking it at the nginx level (returning a 403), but the endpoint still existed at the application layer. On sites using Jetpack or certain mobile apps, XML-RPC serves a purpose. On this site, Jetpack was installed but had zero active modules. The endpoint was pure liability—a vector for brute-force attacks and DDoS amplification with no corresponding benefit, worth blocking at multiple layers.
What the Assessment Revealed
The site wasn’t compromised. It wasn’t running vulnerable software. It wasn’t misconfigured in any way that created immediate risk.
But it wasn’t hardened either. It had accumulated technical debt: unused plugins, legacy configurations, exposed endpoints, missing security constants. Each individual item was minor. Collectively, they represented unnecessary attack surface and deviation from security best practices.
This is the state most WordPress sites exist in. Not breached, not secure—just gradually drifting toward vulnerability as software accumulates and configurations age.
Part Two: The Hardening
Clearing the Underbrush
The first phase was reduction. Security through subtraction.
Nine plugins were removed: the inactive ones, the redundant backup tool, the leftover migration utilities, Hello Dolly. Each one represented code that could contain vulnerabilities, code that required updates, code that served no purpose on this particular site.
The provisioning files were deleted. The .htaccess was cleaned of its orphaned caching rules (with a backup, always a backup). The attack surface shrank.
Strengthening the Core
The wp-config.php file received its missing constants:
define('DISALLOW_FILE_EDIT', true);
define('FORCE_SSL_ADMIN', true);
The first prevents anyone—even a legitimate admin—from editing theme or plugin files through the WordPress dashboard. If an attacker gains admin access, this removes one of their easiest persistence mechanisms. It also prevents well-meaning administrators from making changes that bypass version control.
The second enforces HTTPS for all admin and login operations. Kinsta already forces SSL at the nginx level, but the constant ensures WordPress itself generates correct URLs and doesn’t create mixed-content scenarios. Belt and suspenders.
Disabling What Shouldn’t Exist
XML-RPC presented an interesting challenge. On Apache servers, you can block it with .htaccess rules. Kinsta runs nginx, where .htaccess only handles redirects. The server-level block was already in place (Kinsta returns a 403 for xmlrpc.php requests), but defense in depth suggests handling it at the application layer too.
The solution was a must-use plugin—a PHP file placed in wp-content/mu-plugins/ that loads automatically and can’t be deactivated through the admin interface:
<?php
/**
* Plugin Name: Disable XML-RPC
* Description: Disables XML-RPC functionality
*/
// Disable XML-RPC methods
add_filter('xmlrpc_enabled', '__return_false');
// Remove RSD link from head
remove_action('wp_head', 'rsd_link');
Simple, lightweight, effective. If someone bypasses the server block somehow, the application layer still returns nothing useful.
The Comments Question
During the engagement, the client mentioned they wanted comments disabled sitewide. The site didn’t use comments. They’d never used comments. But WordPress had comments enabled by default, and the infrastructure was still there—dormant but accumulating bot spam. A test revealed comment submissions were being accepted and queued for moderation: shortlinks, pharmaceutical spam, the usual debris that accumulates on any WordPress site with an open comment form.
The easy solution would be a plugin. There are dozens of “disable comments” plugins available. But plugins introduce dependencies, require updates, and can be deactivated by anyone with admin access.
The better solution was another mu-plugin—one that comprehensively disabled comments at every level:
- Closed comments and pingbacks on all post types
- Removed the Comments menu from the admin sidebar
- Removed the comments icon from the admin bar
- Redirected any direct navigation to the comments admin page
- Removed comment-related widgets
- Disabled comment-related REST API endpoints
- Removed comment feed links from page headers
About 80 lines of code that would survive plugin updates, couldn’t be accidentally disabled, and left no trace of comment functionality anywhere in the admin interface.
User Hygiene
One of the three administrators was reviewed with the client. Her role was primarily content editing—she didn’t need the ability to install plugins, modify themes, or manage other users. With the client’s approval, her role was changed from Administrator to Editor.
This isn’t about trust. It’s about blast radius. If her credentials were ever compromised, an Editor account limits what an attacker can do. The principle of least privilege isn’t paranoia; it’s architecture.
Two-Factor Authentication
The final hardening step was enabling 2FA for all users. WordPress doesn’t include this natively, so a plugin was necessary—but a focused, well-maintained one rather than a bloated “security suite.”
The configuration enforced 2FA for all user roles (including the Author account) with a three-day grace period for setup. Users couldn’t disable 2FA from their own profiles. Each team member would need to complete their own device enrollment, but the infrastructure was in place.
Part Three: The Unexpected Find
When Scope Expands Itself
The first sign came before I’d even logged in. When the client created my temporary admin account, WordPress sent the standard welcome email with login credentials. Out of habit, I checked the headers—and immediately saw the problem: the message was failing email authentication checks. This wasn’t something the client had mentioned. They probably didn’t even know it was happening. But it was worth investigating.
A quick look at the email headers from a test message told the story:
- SPF: FAIL (the sending IP wasn’t authorized)
- DKIM: PASS (but signed by the hosting provider’s domain, not theirs)
- DMARC: FAIL (no alignment between the From address and the authenticated domain)
The client’s actual email infrastructure was fine. They used Microsoft 365 for business email, with proper SPF, DKIM, and DMARC records. HubSpot was authorized for marketing sends. Everything was correctly configured.
But WordPress wasn’t part of that configuration. It was sending through the hosting provider’s mail service, which authenticated as the hosting provider’s domain—not the client’s. To receiving mail servers, these messages looked like spoofing attempts.
The fix was straightforward: route WordPress email through Microsoft 365 using the WP Mail SMTP plugin. No DNS changes needed—their SPF already included Microsoft’s servers. Just plugin configuration with the right credentials.
This wasn’t the engagement they hired me for. But when you’re already examining a system systematically, you notice things. Flagging them—with context about what it would take to fix—is part of thorough security work.
Part Four: The Handoff
What Changed
By the end of the engagement, the site had:
-
Reduced attack surface — Nine unnecessary plugins removed, provisioning files deleted, orphaned configurations cleaned up
-
Hardened configuration — File editing disabled, SSL enforced at the application layer, security constants in place
-
Blocked legacy endpoints — XML-RPC disabled at both server and application layers
-
Removed unused functionality — Comments disabled sitewide via mu-plugin
-
Improved access controls — One admin account converted to editor, 2FA required for all users
-
Identified email issue — WordPress transactional email authentication problem discovered and documented with remediation path
What Stayed the Same
Some items were intentionally left alone:
-
An inactive caching plugin the client planned to reactivate during a future optimization project
-
A legacy theme they wanted their developer to test before removal
-
Redis object caching (disabled pending the same optimization work)
Security hardening isn’t about achieving some theoretical maximum. It’s about making practical improvements within the constraints of actual business operations.
Documentation
The client received a comprehensive summary of all changes made, configurations applied, and recommendations for ongoing security posture. This included:
-
Verification steps they could perform themselves
-
Items pending their team’s review
-
Ongoing maintenance recommendations (plugin updates, log monitoring, periodic access reviews)
The temporary admin account created for the engagement was flagged for removal after client sign-off—a reminder that even security work creates artifacts that need cleanup.
The Bigger Picture
What This Engagement Illustrates
This wasn’t a dramatic engagement. No malware discovered, no attackers thwarted mid-breach, no urgent middle-of-the-night remediation.
That’s the point.
Proactive security work is about reducing probability—making the site a harder target, limiting blast radius if something does go wrong, establishing baselines that make anomalies visible. It’s about doing the work before something goes wrong, when the choices are deliberate rather than desperate.
The client’s site was never in immediate danger. It was running current software on quality hosting with no signs of compromise. But it had accumulated the kind of drift that, left unchecked, eventually creates problems. Unused plugins that stop receiving updates. Endpoints that serve no purpose but remain exposed. Configurations that were secure by default but could be hardened further.
The Value of Systematic Assessment
Most of what this engagement fixed wasn’t visible to the client. They couldn’t see that XML-RPC was enabled, or that their .htaccess contained orphaned rules, or that WordPress emails were failing authentication checks. These things don’t announce themselves.
A systematic assessment finds them anyway. Not by looking for specific known problems, but by methodically examining each layer: users, configuration, plugins, files, network exposure, dependencies. The assessment itself is as valuable as the hardening—it establishes what exists, what it does, and whether it should.
Defense in Depth
Several of the hardening measures were technically redundant. Kinsta already blocks XML-RPC at the server level—why add an mu-plugin? Kinsta already forces SSL—why add the constant?
Because security isn’t about preventing specific attacks. It’s about creating layers. If one layer fails—misconfiguration, bypass, zero-day—the next layer still holds. The XML-RPC mu-plugin costs nothing in performance and ensures the endpoint returns nothing useful even if the nginx block is somehow circumvented. The SSL constant costs nothing and ensures WordPress generates correct URLs even if server configuration changes.
Redundancy isn’t waste. It’s architecture.
What “Hardened” Actually Means
A hardened WordPress site isn’t one running a security plugin with a green checkmark. It’s one where:
- Every plugin serves an active purpose
- Configuration follows security best practices
- Attack surface is minimized to what’s actually needed
- Access controls reflect actual role requirements
- Authentication includes a second factor
- Monitoring exists to detect anomalies
- Documentation exists so the next person understands what’s there and why
This client’s site achieved that state. Not through dramatic intervention, but through methodical work—the quiet security that prevents incidents rather than responding to them.
That’s what security hardening actually looks like.
Outcomes
The engagement concluded with all hardening measures implemented and verified. Comments functionality was confirmed disabled in staging before production deployment. The client’s team completed 2FA enrollment within the grace period. The HSTS header was enabled via hosting support ticket.
The email authentication issue—discovered incidentally during the engagement—was documented for the client’s consideration as a separate scope item.
No security incidents have occurred since hardening. Which, of course, is exactly the point.
Related Reading
-
Outdated Plugins: The Weak Link in Your Website's Security
Learn how outdated plugins expose your site to hackers — and how to keep every lock secure. -
Case Study: The Hidden Admin
See what happens when security work comes after the breach instead of before. -
The Rise of Automated Attacks: Why Small Businesses Are Prime Targets
Learn why outdated software is irresistible to automated bots.