Step-by-step WordPress security hardening by Hostnasi Technologies
Version 1.0.2 · Requires WordPress 6.0+ · PHP 8.0+
A WordPress plugin that scans your site against 21 industry-standard security checks, shows a live 0–100% security score, and fixes 14 of those checks automatically — no technical knowledge required.
- Why This Plugin
- Features
- Installation
- Using the Dashboard
- Security Checks Reference
- Manual Steps Guide
- How Auto-Fixes Work
- Compatibility
- Troubleshooting
- File Structure
- Changelog
- Support
A freshly installed WordPress site with default settings typically passes fewer than 5 of these 21 checks. WordPress powers 43% of the web, making it the most targeted platform for automated attacks. This plugin closes the most common gaps in minutes.
Default WordPress site After Hostnasi Security
──────────────────────── ──────────────────────────
Score: ~18% ██░░░░░░░░ Score: 85%+ ████████░░
3–4 checks passing 17–21 checks passing
| Category | Checks | Auto-fixable |
|---|---|---|
| Authentication & login | 4 | 3 |
| wp-config.php hardening | 5 | 3 |
| File & directory permissions | 3 | 3 |
| Updates & plugins | 4 | 1 |
| HTTP security headers | 3 | 3 |
| Information leakage | 3 | 3 |
| Total | 21 | 14 |
Built-in features (no third-party plugins needed):
- Login lockout — blocks IPs after 5 failed attempts for 30 minutes
- Hidden login URL — moves
wp-login.phpto a secret random slug - XML-RPC disable
- Security headers (X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy)
- User enumeration block (
/?author=and REST API/wp/v2/users) - WordPress version hiding (meta tags, feeds, asset query strings)
- wp-config.php constant patching (DISALLOW_FILE_EDIT, FORCE_SSL_ADMIN, WP_DEBUG)
- .htaccess hardening (directory listing, sensitive file protection)
- wp-config.php permission fix (chmod 440)
- readme.html deletion
- Download
hostnasi-security.zipfrom your Hostnasi client area - In your WordPress admin, go to Plugins → Add New → Upload Plugin
- Choose
hostnasi-security.zipand click Install Now - Click Activate Plugin
- Navigate to HN Security in the left sidebar — the scan runs immediately
- Extract
hostnasi-security.zipon your computer - Upload the
hostnasi-security/folder to/wp-content/plugins/on your server - Activate via Plugins → Installed Plugins
| Requirement | Minimum |
|---|---|
| WordPress | 6.0 |
| PHP | 8.0 |
| User role | Administrator |
| Web server | Apache or LiteSpeed (Nginx: partial — see Compatibility) |
Navigate to HN Security in your WordPress admin sidebar after activation.
╭──────╮
│ 73% │ ← Needs work (amber)
╰──────╯
✓ Good 80% and above
⚠ Needs work 50–79%
✗ At risk Below 50%
The score updates live in the browser after each auto-fix — no page reload required.
Each failing check shows one of two things:
- Green "Fix" button — click once; the fix applies via AJAX and the check turns green
- "Manual fix required" tag — the fix must be done outside the plugin (see Manual Steps Guide)
Hidden login URL info box
After enabling the hidden login URL, a green info box at the bottom of the dashboard shows your new login address:
Your hidden login URL: https://yoursite.com/secure-xk3p9mz2/
⚠️ Bookmark this immediately. Direct access to/wp-login.phpwill be blocked for bots — and for you if you forget the slug.
Severity: Critical | Auto-fix: No (manual)
Every brute-force bot on the internet tries admin as the first username. A user account with this username is a permanent open invitation. Rename or delete it.
Detection: username_exists('admin') — fails if any account uses this username.
Severity: Critical | Auto-fix: Yes
Blocks IP addresses that repeatedly fail login — stops credential-stuffing and dictionary attacks without requiring a third-party plugin.
What the fix does:
- Enables a
wp_login_failedhook that tracks failure counts per IP in WordPress transients - After 5 failures within 10 minutes, the IP is blocked at the application layer for 30 minutes
- Correctly reads
CF-Connecting-IPfor sites behind Cloudflare
Severity: Critical | Auto-fix: Yes
Moves the login page to a secret URL slug. Bots cannot attack a login page they cannot find.
What the fix does:
- Generates a random 8-character slug:
secure-[random] - Registers a WordPress rewrite rule pointing the slug to the login logic
- Requests to
/wp-login.phpfrom bots are silently redirected to the homepage - The slug is displayed on the dashboard — bookmark it before enabling
Severity: Critical | Auto-fix: Yes
XML-RPC allows attackers to make thousands of login attempts in a single HTTP request (amplification attacks) and is frequently used for DDoS. Disable it unless your mobile app or Jetpack specifically requires it.
What the fix does:
- Hooks
xmlrpc_enabled→__return_false - Hooks
xmlrpc_methods→__return_empty_array
Severity: Critical | Auto-fix: Yes
The built-in editor in Appearance → Theme File Editor and Plugins → Plugin File Editor lets anyone with admin access run arbitrary PHP code. Disabling it removes this escalation path — so a compromised admin password cannot become a full server compromise.
What the fix does: Inserts define('DISALLOW_FILE_EDIT', true); into wp-config.php
Severity: Critical | Auto-fix: Yes (requires active SSL)
Ensures all admin panel traffic is encrypted. Without this, session cookies can be intercepted on shared or public networks.
What the fix does: Inserts define('FORCE_SSL_ADMIN', true); into wp-config.php
Requires a valid SSL certificate. The fix button will return an error if the site is not currently served over HTTPS.
Severity: Critical | Auto-fix: No (manual — ideally set at install time)
The default wp_ prefix makes SQL injection attacks easier — an attacker who finds an injection vulnerability immediately knows your table names.
Detection: Checks $wpdb->prefix !== 'wp_'
This is safest to change during WordPress installation. Changing it on a live site requires a full database backup and careful find-and-replace across all table names and serialized data.
Severity: High | Auto-fix: Yes
Debug mode leaks file paths, database errors, and stack traces directly to visitors — invaluable information for an attacker mapping your installation.
What the fix does: Inserts or updates define('WP_DEBUG', false); in wp-config.php
Severity: High | Auto-fix: No (manual)
WordPress security keys and salts are used to encrypt session cookies and authentication tokens. Default or missing keys make stolen cookies reusable. The plugin checks that all four primary keys (AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY) are defined and at least 40 characters long.
Generate fresh keys: https://api.wordpress.org/secret-key/1.1/salt/
Severity: Critical | Auto-fix: Yes
On shared hosting, other users on the same server can read world-readable files. wp-config.php contains your database credentials — it should never be readable by anyone except the server process.
What the fix does: chmod(wp-config.php, 0440) — owner and group can read, no write, no world access
Detection: Checks for permissions 400, 440, 600, or 640
Severity: High | Auto-fix: Yes
When a directory has no index.php, Apache shows a file browser by default. This exposes your directory structure, plugin list, uploaded file names, and backup files.
What the fix does: Appends Options -Indexes to the root .htaccess
Severity: High | Auto-fix: Yes
Prevents direct HTTP access to configuration and log files that should never be publicly accessible.
What the fix does: Appends to root .htaccess:
# HNS_PROTECT_DOTFILES
<FilesMatch "^(\.htaccess|\.htpasswd|wp-config\.php|debug\.log|readme\.html|license\.txt)$">
Order Allow,Deny
Deny from all
</FilesMatch>Severity: Critical | Auto-fix: No (action required in Dashboard → Updates)
Outdated WordPress core has publicly disclosed CVEs that automated scanners actively exploit. This check fires whenever a new core version is available.
Detection: get_core_updates() — fails if any update with response != 'latest' is available
Severity: Critical | Auto-fix: No (action required in Dashboard → Updates)
Vulnerable plugins are the #1 attack vector on WordPress. A single unpatched plugin (e.g. an old file manager, form builder, or page builder) can give an attacker shell access regardless of every other security measure in place.
Detection: get_site_transient('update_plugins')->response — fails if non-empty
Severity: Critical | Auto-fix: No (manual deletion required)
Inactive plugins are still on the filesystem and still exploitable — deactivating is not enough. Delete plugins you are not using.
Detection: Compares get_plugins() count against active_plugins option
Severity: High | Auto-fix: Yes
Minor WordPress updates (e.g. 6.4.1 → 6.4.2) are almost always security patches. Enabling auto-updates ensures they are applied without waiting for manual action.
What the fix does: update_option('auto_update_core_minor', true)
All three header checks are controlled by a single option (hns_security_headers_enabled). Enabling any one of them enables all five headers simultaneously.
What the fix does: Hooks send_headers to output:
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Strict-Transport-Security: max-age=31536000; includeSubDomains (HTTPS sites only)
Severity: High | Auto-fix: Yes
Prevents your site from being embedded in <iframe> elements on other domains — the mechanism behind clickjacking attacks.
Severity: High | Auto-fix: Yes
Prevents browsers from guessing (sniffing) the MIME type of responses. Without this, an attacker can upload a file that the browser interprets as executable HTML or JavaScript.
Severity: High | Auto-fix: Yes
Controls what URL information is sent to third-party sites when a visitor clicks a link. strict-origin-when-cross-origin shares only the domain, not the full path, protecting admin URLs from leaking in referrer headers.
Severity: High | Auto-fix: Yes
Attackers enumerate usernames before launching targeted credential attacks. WordPress leaks usernames in two places by default.
What the fix does:
- Redirects
/?author=Nrequests to the homepage for unauthenticated visitors - Removes
/wp/v2/usersand/wp/v2/users/{id}from the REST API for unauthenticated requests
WordPress version hidden
Severity: High | Auto-fix: Yes
The WordPress version appears in the <meta name="generator"> tag, RSS feed headers, and appended to CSS/JS file URLs as ?ver=X.X.X. Knowing the exact version helps attackers target known CVEs.
What the fix does:
- Removes
wp_generatoraction fromwp_head - Filters
the_generatorto return empty string - Strips
?ver=parameter from all enqueued style and script URLs
Severity: High | Auto-fix: Yes
WordPress ships with a readme.html in the web root that publicly displays the exact WordPress version number. This file serves no purpose on a live site.
What the fix does: unlink(ABSPATH . 'readme.html') — checks for both readme.html and README.html
Seven checks cannot be fixed automatically. Here is what to do for each one.
- Go to Users → Add New in your admin panel
- Create a new Administrator account with a unique username (not
admin) - Log out, then log back in as the new account
- Go to Users, find the old
adminaccount, click Delete - When prompted, choose to reassign all content to your new account
Best done at install time. During the WordPress installation wizard, change wp_ to something unique (e.g. hn7x_).
For existing live sites:
- Create a full database backup via cPanel → phpMyAdmin → Export
- Use a plugin such as Brozzme DB Prefix & Tools Addons
- Or contact Hostnasi support — we can do it for you
- Visit https://api.wordpress.org/secret-key/1.1/salt/ to generate a fresh set
- Open
wp-config.phpvia cPanel File Manager or FTP - Find the block starting with
define('AUTH_KEY',and replace the entire block with the generated keys - Save — all current sessions will be invalidated immediately (all users must log in again)
Go to Dashboard → Updates and click Update Now when a core update is available. Alternatively, enable auto-updates for major versions from the same screen.
Go to Dashboard → Updates, select all plugins with pending updates, and click Update Plugins. Do this before applying other security fixes — a patched plugin is always the priority.
- Go to Plugins → Installed Plugins
- Use the Inactive filter link at the top
- Select all inactive plugins → Bulk Actions → Delete → Apply
All fixes are applied via a secure AJAX request from the dashboard:
- Requests are verified with a WordPress nonce (
hns_fix) - Only administrator users can trigger fixes
- The fix callback is checked against a whitelist of 14 allowed method names before execution — no arbitrary code execution is possible
The plugin reads wp-config.php into memory, then either:
- Updates an existing
define('CONSTANT', ...)using a regex replace - Inserts a new
define()before the/* That's all, stop editing! */anchor
The file is written back with LOCK_EX to prevent race conditions. If the file is not writable (e.g. chmod 440), the fix returns a manual instruction instead of silently failing.
Each .htaccess fix uses a named marker comment (e.g. # HNS_NO_LISTING) to check for prior application. All .htaccess changes are idempotent — running the same fix twice will not create duplicate rules.
Uses WordPress transients (no additional database tables):
hns_fail_{md5(ip)}— incremented on each failed login, expires after 10 minuteshns_lock_{md5(ip)}— set when threshold is reached, expires after 30 minutes
Hidden login URL
Uses WordPress rewrite rules — no .htaccess modification required:
add_rewrite_rule('^secure-[slug]/?$', 'index.php?hns_login=1', 'top');The slug is stored in wp_options as hns_login_slug. To recover it if locked out:
SELECT option_value FROM wp_options WHERE option_name = 'hns_login_slug';| Environment | Status |
|---|---|
| WordPress 6.0–6.7 | ✅ Fully supported |
| PHP 8.0, 8.1, 8.2, 8.3 | ✅ Fully supported |
| Apache | ✅ All features |
| LiteSpeed | ✅ All features |
| Nginx | .htaccess fixes have no effect — apply equivalent location blocks manually |
| Cloudflare proxy | ✅ Lockout reads CF-Connecting-IP |
| WordPress Multisite | ❌ Not tested — single-site installs only |
| Wordfence / Sucuri | ✅ Fully compatible — complementary, no conflicts |
| WP Super Cache / W3TC | ✅ Compatible |
| LiteSpeed Cache | ✅ Compatible |
If your server runs Nginx, add these to your site's server {} block:
# Disable directory listing
autoindex off;
# Protect sensitive files
location ~* ^/(\.htaccess|wp-config\.php|debug\.log|readme\.html|license\.txt)$ {
deny all;
}Locked out after enabling the hidden login URL
The login slug is stored in the database. Retrieve it via:
cPanel → phpMyAdmin:
SELECT option_value FROM wp_options WHERE option_name = 'hns_login_slug';WP-CLI:
wp option get hns_login_slugNavigate to https://yoursite.com/{slug}/ to log in.
To disable the feature entirely via WP-CLI:
wp option update hns_hide_login_enabled 0
wp rewrite flushAfter applying the chmod 440 fix, wp-config.php becomes non-writable by the web process. To apply further wp-config patches:
Via SSH:
chmod 640 wp-config.php
# Apply fix via dashboard
chmod 440 wp-config.phpOr add the define() manually in cPanel File Manager using the Text Editor (which bypasses file permissions at the OS level).
- Clear your caching plugin's file cache
- On Nginx servers,
.htaccesshas no effect — use the Nginx equivalents above - Verify
.htaccessis not overridden by a parent directory'sAllowOverride Nonedirective
The plugin checks is_ssl() before applying this constant. If your SSL terminates at a load balancer or Cloudflare, WordPress may not detect HTTPS automatically. Add this to wp-config.php first:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}Then retry the fix.
Scores recalculate live via JavaScript after each AJAX fix. If a check remains red:
- Hard refresh the page (
Ctrl+Shift+R/Cmd+Shift+R) - The fix may have been applied but WordPress needs a rewrite flush — go to Settings → Permalinks and click Save Changes
- Some checks (e.g.
DISALLOW_FILE_EDIT) require the constant to be loaded — it may not be detected until the next page load afterwp-config.phpis patched
hostnasi-security/
├── hostnasi-security.php # Plugin bootstrap, constants, activation hook
├── readme.txt # WordPress.org plugin readme
├── README.md # This file
├── includes/
│ ├── class-hns-checks.php # 21 check definitions + detection callbacks
│ ├── class-hns-actions.php # Auto-fix implementations
│ └── class-hns-admin.php # Dashboard UI, AJAX handler, runtime hooks
└── assets/
├── admin.css # Dashboard styles (Hostnasi brand)
└── admin.js # AJAX fix button handler + live score update
| Option key | Purpose |
|---|---|
hns_login_lockout_enabled |
Lockout feature toggle |
hns_lockout_attempts |
Failure threshold (default: 5) |
hns_lockout_duration |
Block duration in minutes (default: 30) |
hns_login_slug |
Hidden login URL slug |
hns_hide_login_enabled |
Hidden login feature toggle |
hns_xmlrpc_disabled |
XML-RPC disable toggle |
hns_security_headers_enabled |
Security headers toggle |
hns_user_enum_blocked |
User enumeration block toggle |
hns_version_hidden |
Version hiding toggle |
auto_update_core_minor |
WordPress native auto-update option |
All options are cleaned up on plugin deletion (add uninstall.php for production hardening).
- Added visible plugin version in the admin dashboard header
- Updated release metadata to 1.0.2
- Removed uploads PHP execution blocking check/fix from the hardening workflow
- Updated checklist and release metadata
- Initial release
- 21 security checks across 6 categories
- 14 one-click auto-fixes
- Built-in login lockout (no external plugin required)
- Hidden login URL with random slug generation
- wp-config.php patching (DISALLOW_FILE_EDIT, FORCE_SSL_ADMIN, WP_DEBUG)
- .htaccess hardening (Options -Indexes, sensitive file protection)
- HTTP security headers (X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy)
- User enumeration blocking (author query + REST API)
- WordPress version hiding (meta, feeds, asset URLs)
- readme.html deletion
- Live security score with colour-coded grade
- Admin notice when score drops below 50%
- Cloudflare IP header support
Built and maintained by Hostnasi Technologies — your WordPress hosting provider in Tanzania.
| Channel | Details |
|---|---|
| Support portal | https://hostnasi.com/support |
| support@hostnasi.com | |
| Client area | https://hostnasi.com/client |
| Website | https://hostnasi.com |
If you find a security issue in this plugin itself, please disclose it responsibly via email rather than opening a public issue.
Hostnasi Technologies · Dar es Salaam, Tanzania · hostnasi.com