What does it mean to harden WordPress?
Hardening reduces your attack surface. Every file, endpoint, user account, and server configuration that’s accessible to the outside world is a potential entry point. Hardening systematically closes the ones you don’t need and restricts the ones you do.
Most WordPress security advice starts and ends with “install a security plugin.” That’s like putting a lock on your front door while leaving every window open. Security plugins operate inside WordPress. They can’t protect against attacks that target the server, the database connection, or PHP itself. Real hardening works at layers the plugin never touches.
This guide covers what to do beyond plugins. If you haven’t already run through the basics (updates, strong passwords, 2FA), start with our WordPress security audit checklist first. This post assumes you’ve covered those fundamentals.
Lock down wp-config.php
Your wp-config.php file contains database credentials, authentication keys, and configuration constants. It’s the single most sensitive file in your WordPress installation.
Move it above the web root
WordPress automatically checks one directory above the web root for wp-config.php. Moving it there means it’s not accessible via any URL, even if your server misconfigures PHP processing:
/home/username/wp-config.php ← moved here
/home/username/public_html/ ← web root
/home/username/public_html/wp-admin/
/home/username/public_html/wp-content/
Add security constants
// Disable file editing from wp-admin
define('DISALLOW_FILE_EDIT', true);
// Disable plugin/theme installation from wp-admin
define('DISALLOW_FILE_MODS', true);
// Force SSL for admin
define('FORCE_SSL_ADMIN', true);
// Limit post revisions
define('WP_POST_REVISIONS', 5);
// Disable WordPress debug display on production
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
DISALLOW_FILE_MODS is aggressive. It prevents plugin and theme updates from wp-admin, meaning you’ll need to update via SFTP or WP-CLI. But it also means an attacker who gains admin access can’t install a malicious plugin through the dashboard.
Set correct permissions
chmod 600 wp-config.php
On shared hosting where your user and the web server user differ, use 640 instead. The file should be readable by PHP but not writable by the web process.
Server-level hardening with .htaccess
If you’re on Apache (most cPanel shared hosting), .htaccess rules execute before WordPress loads. They stop malicious requests at the server level.
Block access to sensitive files
# Block access to wp-config.php
<Files wp-config.php>
Order Allow,Deny
Deny from all
</Files>
# Block access to .htaccess itself
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
# Block access to xmlrpc.php
<Files xmlrpc.php>
Order Allow,Deny
Deny from all
</Files>
# Block access to wp-cron.php from external requests
<Files wp-cron.php>
Order Allow,Deny
Deny from all
Allow from 127.0.0.1
</Files>
Disable PHP execution in uploads
The wp-content/uploads/ directory should never execute PHP. If an attacker uploads a PHP backdoor disguised as an image, this rule prevents it from running:
# In wp-content/uploads/.htaccess
<Files "*.php">
Order Allow,Deny
Deny from all
</Files>
Create this file manually. WordPress doesn’t generate it by default.
Disable directory browsing
Options -Indexes
Without this, anyone can browse your directory structure by visiting /wp-content/plugins/ directly. That reveals exactly which plugins you run and their versions.
PHP configuration hardening
PHP settings control what WordPress can do at the language level. Restricting dangerous functions limits what malware can accomplish even if it gets uploaded.
Disable dangerous functions
In your php.ini or .user.ini:
disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_multi_exec, parse_ini_file, show_source, eval
This breaks WP-CLI (which needs proc_open). If you use WP-CLI, remove proc_open from the list. The tradeoff is worth understanding: these functions let PHP execute system commands. Malware uses them to download additional payloads, send spam, or pivot to other services on the server.
Restrict file uploads
file_uploads = On
upload_max_filesize = 10M
max_file_uploads = 5
Keep upload size to what you actually need. A 256MB upload limit is an invitation for abuse.
Hide PHP version
expose_php = Off
No reason to advertise your PHP version in response headers. It helps attackers identify which exploits apply to your server.
File permission hardening
Incorrect file permissions are one of the most common security gaps on WordPress sites, especially after migrations or manual file uploads.
| Path | Correct Permission | Why |
|---|---|---|
| Directories | 755 | Owner can read/write/execute. Group and others can read/execute only. |
| Files | 644 | Owner can read/write. Group and others can read only. |
| wp-config.php | 600 or 640 | Only the owner (or owner + group) can read. Nobody can write. |
| .htaccess | 644 | Readable by Apache, writable only by owner. |
| wp-content/uploads/ | 755 | Writable by the web server for media uploads. |
To fix permissions in bulk via SSH:
find /path/to/wordpress -type d -exec chmod 755 {} \;
find /path/to/wordpress -type f -exec chmod 644 {} \;
chmod 600 /path/to/wordpress/wp-config.php
On cPanel shared hosting without SSH, use File Manager’s permission settings or ask your host to run these commands.
Disable XML-RPC
XML-RPC (xmlrpc.php) is a legacy API that allows external applications to communicate with WordPress. It’s also the most common brute-force vector after wp-login.php. Attackers use the system.multicall method to test hundreds of password combinations in a single request, bypassing login attempt limits.
Unless you use the WordPress mobile app, Jetpack, or a service that specifically requires XML-RPC, disable it completely. The .htaccess rule above blocks it at the server level. You can also add this to your theme’s functions.php:
add_filter('xmlrpc_enabled', '__return_false');
The .htaccess approach is better because it stops the request before PHP even loads. The filter approach still lets PHP process the request before returning a denial.
Security headers
HTTP security headers instruct browsers on how to handle your site’s content. They prevent entire classes of attacks (clickjacking, XSS, MIME sniffing) with zero performance cost.
Add these to your .htaccess or server configuration:
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
Test your headers at securityheaders.com after implementing. Aim for an A grade minimum.
What plugins can’t do
Security plugins like Wordfence, Sucuri, and iThemes Security add value. They monitor file changes, limit login attempts, and scan for known malware signatures. But they have fundamental limitations:
- They can’t modify server configuration (Apache/Nginx rules, PHP settings)
- They can’t set file permissions below what PHP’s user has access to
- They can’t protect against attacks that bypass PHP entirely (direct file access, SSH brute force)
- They add processing overhead to every request (the plugin itself runs on every page load)
- They can be disabled by an attacker who gains admin access
Think of security plugins as a monitoring layer, not a protection layer. The actual protection comes from the server-level hardening described above. The plugin tells you when something goes wrong. The hardening prevents things from going wrong in the first place.
Next step
Start with wp-config.php. Add DISALLOW_FILE_EDIT and FORCE_SSL_ADMIN today. Then create the .htaccess file in wp-content/uploads/ to block PHP execution. These two changes take five minutes and close the most commonly exploited gaps on WordPress sites.
After that, check your file permissions and disable XML-RPC. Run securityheaders.com against your domain and add any missing headers. The full hardening process takes about an hour for a standard WordPress site on cPanel hosting.