Practical WordPress Hardening After a Real Attack

Key Principle for Beginners

WordPress security is not about installing more tools. It is about removing places where malicious code can execute.

Most real-world WordPress attacks do not require administrator access. Instead, attackers upload PHP files into writable directories and repeatedly execute them from there. If those execution paths are removed, reinfection usually stops.

  • Reduce execution paths
  • Reduce privilege exposure
  • Protect configuration files
  • Create a clean restore point

Site Context Used in This Guide

  • WordPress running on shared hosting
  • WordPress root directory typically public_html
  • Changes made through hosting File Manager and wp-admin
  • No SSH or terminal access required

If your hosting uses a different web root, replace public_html with the correct directory.


What the Attack Looked Like

The incident that led to this hardening process revealed that malicious files were being placed in writable server directories rather than directly inside theme or plugin files.

WordPress functioned mainly as an entry point, while the malicious scripts attempted to execute from writable folders and temporary server locations.

This is why the hardening strategy focuses on blocking execution paths rather than simply deleting malware files.


Step 1 — Secure the Root .htaccess

Location

public_html/.htaccess

Code

# Disable directory listing
Options -Indexes

# Protect wp-config.php
<Files wp-config.php>
Require all denied
</Files>

# Block XML-RPC completely
<Files xmlrpc.php>
Require all denied
</Files>

# Block access to hidden or backup files
<FilesMatch "^\\.(env|git|ht|svn)|\\.(bak|backup|old|sql)$">
Require all denied
</FilesMatch>

Purpose

  • Prevents directory listing
  • Blocks direct access to configuration files
  • Disables XML-RPC attack surface
  • Protects backup and environment files

Step 2 — Block PHP Execution in Writable Folders

Create a .htaccess file in the following directories:


public_html/wp-includes/
public_html/wp-content/uploads/
public_html/wp-content/plugins/
public_html/wp-content/cache/

Code

<FilesMatch "\.php$">
Require all denied
</FilesMatch>

Even if a malicious PHP file appears in these folders, the server will refuse to execute it.


Step 3 — Remove Abused or Unused Folders

Example folder discovered during investigation:

public_html/wp-admin/maint/

If a directory like this is not required by WordPress, delete it using the hosting file manager.

Unused folders often become storage locations for malicious scripts.


Step 4 — Harden wp-config.php

Location

public_html/wp-config.php

Settings


define('DISALLOW_FILE_EDIT', true);
define('FORCE_SSL_ADMIN', true);
define('WP_DEBUG', false);

These settings:

  • Disable the WordPress file editor
  • Force secure admin connections
  • Prevent debugging information from leaking

Step 5 — Minimal Theme Hardening

Location

public_html/wp-content/themes/YOUR-THEME/functions.php

// Remove WordPress version from head
remove_action('wp_head', 'wp_generator');

// Disable REST API user enumeration
add_filter('rest_endpoints', function ($endpoints) {
    if (isset($endpoints['/wp/v2/users'])) {
        unset($endpoints['/wp/v2/users']);
    }
    return $endpoints;
});

// Disable XML-RPC pingbacks
add_filter('xmlrpc_methods', function ($methods) {
    unset($methods['pingback.ping']);
    return $methods;
});

This reduces automated discovery and fingerprinting.


Step 6 — Remove Public Information Files

Delete if present:


public_html/readme.html
public_html/license.txt

These files may reveal WordPress version information to automated scanners.


Step 7 — Correct File Permissions


Folders: 755
Files: 644
wp-config.php: tighter permissions if supported

This limits what compromised scripts can modify on the server.


Step 8 — Audit WordPress Users

Check user accounts in:

wp-admin → Users

Actions taken:

  • Verified a single administrator
  • Confirmed legitimate author account
  • Removed unnecessary subscriber accounts

Step 9 — Rotate Database Credentials

Change the database user password in the hosting control panel and update it inside:

public_html/wp-config.php
define('DB_PASSWORD', 'NEW_PASSWORD_HERE');

This prevents database access using previously exposed credentials.


Step 10 — Create a Final Backup

Create a full backup including files and database after confirming the site is clean.

Example backup label:

post-hardening-clean-state

Additional Hardening — API Tokens and FTP Accounts

Server-level access credentials can bypass WordPress security entirely. After any security incident, review these access methods.

API Tokens

Many hosting panels, cloud services, and automation tools use API tokens for authentication.

Recommended actions:

  • Review all existing API tokens in the hosting control panel
  • Delete tokens that are no longer required
  • Regenerate tokens that may have been exposed
  • Store tokens securely and avoid sharing them through email or plain text

Rotating API tokens prevents automated systems from continuing to access your server using previously issued credentials.

FTP Accounts

FTP access allows direct modification of site files and can bypass WordPress login security.

Review FTP accounts in the hosting control panel:

  • Remove unused FTP users
  • Change passwords for active accounts
  • Use strong unique passwords
  • If possible, restrict FTP access to specific directories

Reducing unnecessary FTP accounts removes another potential entry point.


What This Guide Did Not Do

  • No security plugins
  • No server shell commands
  • No cron manipulation
  • No continuous malware scanning

The goal was to reduce the attack surface rather than add operational complexity.


Final Outcome

  • Execution paths for malicious scripts restricted
  • Configuration files protected
  • Unused folders removed
  • User privileges verified
  • Database credentials rotated
  • Server access tokens and FTP accounts reviewed
  • A clean backup state created

The result is a stable and hardened WordPress installation requiring minimal ongoing maintenance.