name: wordpress-security description: "Use when hardening WordPress sites, auditing security configurations, applying security measures, or working with the MCP Content Manager Security module: wp-config.php constants, .htaccess rules, HTTP headers, login protection, REST API restrictions, file permissions, and security scoring." compatibility: "Targets WordPress 6.0+ on PHP 7.4+. Uses MCP Content Manager Premium v2.3+ abilities for automated security operations. Apache/LiteSpeed for .htaccess measures; Nginx requires manual server config."
WordPress Security Hardening
When to use
- Running a security audit on a WordPress site
- Applying hardening measures (wp-config.php constants, .htaccess rules, HTTP headers)
- Protecting login (brute force limiting, custom login URL, session management)
- Restricting REST API endpoints to prevent user enumeration
- Blocking XML-RPC abuse or pingback attacks
- Fixing file/directory permissions
- Implementing Content-Security-Policy headers
- Changing database table prefix
- Moving wp-config.php outside public directory
- Auditing weak passwords
- Any request mentioning "securizar", "hardening", "seguridad WordPress", or "security audit"
Inputs required
- Whether MCP Content Manager Premium v2.3+ is installed (abilities available)
- Server type: Apache, LiteSpeed, or Nginx (determines .htaccess compatibility)
- Whether SSL/HTTPS is properly configured (required for HSTS, FORCE_SSL_ADMIN)
- Current WordPress version and PHP version
- Active plugins that may conflict (Jetpack, WooCommerce, caching plugins)
- Whether Cloudflare or reverse proxy is in use (affects IP detection)
Procedure
0) Always start with audit — "Informar antes de actuar"
CRITICAL PRINCIPLE: Never apply security changes without first auditing and informing the user.
1. Run mcm/security-audit to get current state and score (0-100)
2. Present results grouped by risk level:
🟢 SAFE — Can apply directly without side effects
🟡 CAUTION — May affect plugins/themes; explain risks first
🔴 CRITICAL — Can break the site; require explicit backup confirmation
3. The USER decides, the plugin executes
If MCP Content Manager is available:
Use ability: mcm/security-audit
If not available, check manually:
// Check wp-config.php constants
defined('DISALLOW_FILE_EDIT') // Should be true
defined('FORCE_SSL_ADMIN') // Should be true if HTTPS available
defined('WP_DEBUG_DISPLAY') // Should be false in production
// Check .htaccess rules
// Look for directory listing protection, PHP execution blocks, sensitive file protection
// Check HTTP response headers
// X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy
0.1) Recommend Two-Factor Authentication
If the audit shows no 2FA plugin is installed, recommend the official WordPress Two-Factor plugin:
- Plugin: Two-Factor
- MCM can install it via
mcm/install-plugin(slug:two-factor), but configuration must be done manually by the user - Recommend enabling it at minimum for all Administrator accounts
- 2FA is one of the most effective protections against unauthorized access
1) Apply SAFE measures first (🟢)
These have NO known side effects and can be applied in batch:
| ID | Measure | Points |
|---|---|---|
file_edit_disabled |
DISALLOW_FILE_EDIT in wp-config.php | 5 |
wpconfig_htaccess_protection |
Block HTTP access to wp-config.php | 5 |
disable_directory_listing |
Options -Indexes in .htaccess | 5 |
uploads_php_execution_block |
Block PHP in wp-content/uploads/ | 5 |
sensitive_files_protection |
Block access to .sql, .bak, .log files | 4 |
login_attempt_limit |
Progressive brute force lockout | 10 |
disable_pingbacks_trackbacks |
Close pingbacks globally | 3 |
remove_pingback_header |
Remove X-Pingback header | 2 |
hotlinking_protection |
Block hotlinking from external domains | 2 |
remove_version_info |
Remove WordPress version from source | 3 |
idle_session_logout |
Auto-logout after inactivity | 3 |
weak_password_audit |
Check for common passwords | 5 |
If MCP Content Manager is available:
Use ability: mcm/security-apply-safe
Optional: exclude specific measures with "exclude" parameter
2) Apply CAUTION measures with user consent (🟡)
Always explain risks before applying:
| ID | Measure | Points | Risk |
|---|---|---|---|
xmlrpc_protection |
Block/limit XML-RPC | 8 | Jetpack, WP mobile app need it |
rest_api_protection |
Restrict sensitive endpoints | 8 | Some plugins use public REST API |
user_enumeration_protection |
Block author enumeration | 7 | Author archives return 404 |
force_ssl_admin |
Force HTTPS on admin | 10 | Breaks if SSL not configured |
security_headers |
HTTP security headers | 7 | Varies by header |
custom_login_url |
Change /wp-login.php URL | 3 | Users may lose access |
file_permissions_fix |
Fix file/dir permissions | 5 | Server ACLs may differ |
disable_file_mods |
DISALLOW_FILE_MODS | — | Disables MCP itself! |
For XML-RPC, recommend selective mode:
- Blocks dangerous methods (system.multicall, pingback.ping, user enumeration)
- Preserves safe methods for mobile app and Jetpack
For REST API, NEVER block:
/wp-json/mcp/*— MCP adapter endpoints/wc/v3/*— WooCommerce API/wp/v2/posts,/wp/v2/pages— Public content
For FORCE_SSL_ADMIN, always pre-verify:
$response = wp_safe_remote_get('https://' . $_SERVER['HTTP_HOST'] . '/wp-admin/', array('sslverify' => true));
if (is_wp_error($response)) {
// DO NOT enable FORCE_SSL_ADMIN
}
3) Handle CRITICAL measures with extreme care (🔴)
These require backup_confirmed=true and explicit user consent:
| ID | Measure | Risk |
|---|---|---|
database_prefix_change |
Change DB table prefix | SQL errors = site down |
move_wpconfig_outside |
Move wp-config.php up | Site inaccessible if fails |
| HSTS header | Strict-Transport-Security | Irreversible if SSL removed |
| CSP header (enforce mode) | Content-Security-Policy | Breaks scripts/styles easily |
Database prefix change protocol:
- Require full database backup
- Use SQL transactions (START TRANSACTION / COMMIT / ROLLBACK)
- Only rename 12 core tables, NOT plugin tables
- Update usermeta keys and user_roles option
- Update $table_prefix in wp-config.php
CSP implementation — two phases:
- Phase 1: Content-Security-Policy-Report-Only (collect violations)
- Phase 2: Content-Security-Policy (enforce after validation)
- Never skip Phase 1; WordPress + Gutenberg inject inline scripts/styles
4) Verify after every change
After applying ANY measure, verify site health:
1. Check frontend loads (HTTP 200)
2. Check wp-admin loads (HTTP 200)
3. Check REST API responds (MCP endpoints)
4. If any check fails → auto-revert the measure
If MCP Content Manager is available, this is done automatically by mcm_security_verify_site_health().
5) Server compatibility
Apache/LiteSpeed: All .htaccess measures work natively.
Nginx: .htaccess measures (2.4, 2.5, 2.6, 2.7, 2.17) are NOT supported. Instead:
- Inform the user with equivalent Nginx config blocks
- Mark measures as
not_applicablein audit results - PHP-based measures (hooks, filters, wp-config constants) work on all servers
6) Recovery mechanisms
Custom Login URL recovery:
- Use
mcm/security-emergency-loginability (restores /wp-login.php for 15 min) - Create empty file
mcm-emergency-login.txtin ABSPATH via FTP
wp-config.php recovery:
- MCM_File_Manager creates automatic backups before every write
- Use
mcm/restore-backupability to restore
General recovery:
- MCM Sentinel mu-plugin provides emergency access
- All .htaccess changes use delimited blocks for easy manual removal
Verification
- Security audit ran before any changes were applied
- User was informed of risks for all 🟡 and 🔴 measures
- No 🔴 CRITICAL measure applied without backup confirmation
- MCP adapter endpoints (
/wp-json/mcp/*) are NOT blocked by REST API protection - WooCommerce endpoints are NOT blocked if WooCommerce is active
- Site frontend, admin, and REST API all respond after changes
- .htaccess rules use MCM delimited blocks:
# BEGIN MCM Security - [id]/# END MCM Security - [id] - wp-config.php changes use safe write protocol (read perms → elevate → write → restore perms)
- Login attempt limiter uses SHA256 hashed IPs, not plain IPs
- CSP started in Report-Only mode, not enforcement
- Server type detected — .htaccess measures skipped on Nginx
- Custom login URL has emergency recovery mechanism configured
Failure modes / debugging
- "Could not read wp-config.php" — File permissions too restrictive. Check that the web server user can read the file. Try
chmod 644 wp-config.phptemporarily. - Admin inaccessible after FORCE_SSL_ADMIN — SSL certificate issue. Remove the constant via FTP or use MCM Sentinel recovery.
- Site broken after .htaccess change — Remove the specific MCM Security block from .htaccess via FTP. Look for
# BEGIN MCM Security - [measure_id]and delete to matching# END. - Login page returns 404 after custom URL — Use emergency login ability or create
mcm-emergency-login.txtin WordPress root via FTP. - REST API blocked for legitimate plugin — Add plugin's API namespace to the whitelist in REST API protection config.
- CSP breaking site functionality — Switch from enforcement to Report-Only mode, review violation reports, adjust policy.
- Database prefix change failed — Transaction should auto-rollback. If not, restore from backup. Check that MySQL user has ALTER TABLE and RENAME TABLE privileges.
- Brute force limiter blocking legitimate users — Clear the transient:
delete_transient('mcm_login_attempts_' . hash('sha256', $user_ip)).