WordPress Security Hardening: Stop Brute Force, SQLi, and RCE Before They Hit

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Quick Start: Lock Down the Basics in 5 Minutes

Your WordPress login page is still at /wp-login.php with no rate limiting? You’re already bleeding. I’ve seen fresh installs rack up 10,000 failed login attempts in a single night. Here’s how to stop it fast.

Step 1: Install Wordfence or Solid Security

Either plugin covers 80% of your attack surface immediately. For most setups, go with Wordfence Free — it ships with a real-time firewall, login rate limiting, and malware scanner. No paid plan required.

Go to wp-admin → Plugins → Add New, search Wordfence, install and activate. Run the setup wizard and enable these three things:

  • Brute force protection (lockout after N failed attempts)
  • Two-factor authentication for admin accounts
  • Firewall in Extended Protection mode (requires a line in .htaccess or php.ini so it loads before WordPress)

Step 2: Change the Admin Username

Admin account still named admin? Attackers already know half your credentials. The fix takes two minutes: create a new admin user with a different name, log in as that account, then delete the old admin user — reassigning its posts to the new one when prompted.

For the password, I use toolcraft.app/en/tools/security/password-generator — it runs entirely in the browser, no data sent over the network, no API calls, no logging. That matters when generating credentials for production systems.

Step 3: Disable XML-RPC if You Don’t Need It

XML-RPC gets abused for brute-force amplification: one HTTP request can test hundreds of username/password pairs simultaneously. Unless you’re using the WordPress mobile app or Jetpack, shut it off.

Apache users, add this to .htaccess:

<Files xmlrpc.php>
  Order Deny,Allow
  Deny from all
</Files>

Nginx users, add this to the server block:

location = /xmlrpc.php {
    deny all;
    return 403;
}

Deep Dive: SQL Injection and RCE Prevention

How SQLi Hits WordPress

SQLi rarely comes through WordPress core — it comes through vulnerable plugins. A plugin with a $wpdb->query() call that concatenates raw user input is the classic entry point. No login required. The attack goes straight through a form field, search box, or REST endpoint.

Wordfence’s firewall inspects incoming requests against known patterns: UNION SELECT, OR 1=1, stacked queries, encoded variants. Signature-based, not perfect — but it catches the automated scanner runs that probe every public site within hours of going live.

The real fix is keeping plugins current. I run this on my VPS to catch anything lagging behind:

# Check which plugins have updates pending
wp plugin list --update=available --format=table --path=/var/www/html

# Auto-update everything nightly at 3am
0 3 * * * /usr/local/bin/wp plugin update --all --path=/var/www/html --quiet

Auto-update everything except plugins you’ve customized. For those, a monthly calendar reminder works fine.

Blocking RCE Vectors

RCE hits WordPress through three main routes:

  1. File upload vulnerabilities — a plugin accepts arbitrary file types, including .php
  2. Theme/plugin editor — someone with compromised admin credentials edits PHP directly inside wp-admin
  3. Deserialization gadgets — rare but devastating in certain plugin combinations

Disable the file editor. Add these two lines to wp-config.php:

define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true ); // Also blocks plugin/theme installs from wp-admin

Block PHP execution in upload directories. Nginx config:

location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
    return 403;
}

Apache equivalent:

<Directory "/var/www/html/wp-content/uploads">
  <Files "*.php">
    Order Deny,Allow
    Deny from all
  </Files>
</Directory>

Hardening wp-config.php

Database credentials, secret keys, table prefix — it’s all in wp-config.php. Move it one directory above the web root so it’s unreachable by HTTP:

mv /var/www/html/wp-config.php /var/www/wp-config.php

# WordPress checks one level up automatically — no code change needed
chmod 600 /var/www/wp-config.php
chown www-data:www-data /var/www/wp-config.php

Also regenerate your salts. Hit https://api.wordpress.org/secret-key/1.1/salt/, copy the output, and replace the AUTH_KEY / SECURE_AUTH_KEY block in wp-config.php. Every existing session gets invalidated immediately — logged-in users get kicked out. That’s the intended behavior after a suspected compromise.

Advanced: Server-Level Hardening

HTTP Security Headers

Zero performance cost, real protection against clickjacking, MIME sniffing, and reflected XSS. Add these to your Nginx config:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline';" always;

After deploying, run your domain through securityheaders.com. Aim for a B+ or higher — anything below that means something important is missing or misconfigured.

Rate Limiting at the Nginx Level

Wordfence’s brute force protection fires at the PHP layer — meaning WordPress already booted, connected to the database, and burned CPU just to reject the request. Push that block earlier:

http {
    limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m;

    server {
        location = /wp-login.php {
            limit_req zone=wp_login burst=3 nodelay;
            limit_req_status 429;
            # ... rest of your config
        }
    }
}

Five login attempts per minute per IP, burst of 3. A script hammering 100 requests per second gets a 429 before PHP even wakes up. For most shared hosting attacks, this alone cuts noise by 95%+.

Database Privileges: Least Privilege

Many default setups hand the WordPress DB user ALL PRIVILEGES. WordPress actually needs only eight: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, and INDEX. Strip everything else:

REVOKE FILE, SUPER, PROCESS, RELOAD ON *.* FROM 'wp_user'@'localhost';

SHOW GRANTS FOR 'wp_user'@'localhost';

Revoking FILE is the important one. Without it, an attacker who does slip through SQLi can’t call LOAD_FILE() to read server files or INTO OUTFILE to drop a PHP backdoor.

Practical Tips from the Field

Audit Plugin Count

Every plugin is an attack surface. Full stop. I’ve taken over WordPress installs with 40+ active plugins — half abandoned, some with CVEs sitting unpatched for two years. Run this and cut anything with no updates since 2022:

wp plugin list --path=/var/www/html --format=table | grep -v 'active'

Set Up File Integrity Monitoring

Wordfence catches modified files, but OS-level baseline checks are faster and don’t require a plugin running:

# Snapshot all PHP files outside uploads
find /var/www/html -name '*.php' -not -path '*/wp-content/uploads/*' \
  | sort | xargs md5sum > /root/wp_baseline.md5

# Weekly cron check
md5sum -c /root/wp_baseline.md5 2>/dev/null | grep FAILED

FAILED in the output means a PHP file changed since your snapshot. Investigate before assuming it’s a routine update — it may not be.

Backup Before You Harden

Moving wp-config.php, enabling CSP headers, or disabling XML-RPC can quietly break integrations you forgot existed. Snapshot first, always:

# WP-CLI export
wp db export /root/wp_backup_$(date +%Y%m%d).sql --path=/var/www/html

# Or with mysqldump
mysqldump -u root -p wordpress_db > /root/wp_backup_$(date +%Y%m%d).sql

Monitor Login Activity

Wordfence surfaces recent logins in its dashboard. For deeper visibility, the WP Activity Log plugin — paired with a syslog forwarder — gives you a searchable audit trail. When something breaks at 3am, you want a timeline, not guesswork.

Security isn’t a one-time checklist. It’s ongoing maintenance. Plugin-level blocking, Nginx rate limiting, database privilege lockdown, and monthly audits together put you far ahead of the automated campaigns that probe every public WordPress install around the clock. Start with the 5-minute steps today. Layer in the server config this week. The rest can wait for a weekend.

Share: