Type something to search...
How to Protect a WordPress Website from Malware and Hackers?

How to Protect a WordPress Website from Malware and Hackers?

Most WordPress malware infections don't come from a sophisticated attack; they come from an outdated plugin, a weak password, or a leaked config file. WordPress core itself has a solid security track record, but it powers such a large share of the web that automated bots are constantly scanning for exactly these kinds of low-effort openings, and they don't care whether your site is a personal blog or a six-figure e-commerce store, only whether it's vulnerable.

Our guide on how to secure a WordPress website covers the foundational habits: updates, passwords, backups, SSL. This one goes a level deeper, into the specific hardening techniques, file-level protections, and code you can put directly into wp-config.php, .htaccess, and functions.php to close off the attack paths malware and hackers actually use.

How Malware Actually Gets Into a WordPress Site

Before hardening anything, it's worth understanding the handful of paths that account for the overwhelming majority of WordPress compromises:

  • Outdated plugins and themes. The single most common entry point by a wide margin. A known vulnerability in an old plugin version is public information; once a CVE is published, automated bots start scanning for sites still running the vulnerable version within hours.
  • Weak or reused admin credentials. Brute-force and credential-stuffing bots try common passwords and leaked password lists from unrelated data breaches against thousands of WordPress login pages simultaneously.
  • Nulled or pirated themes and plugins. "Free" premium plugins downloaded from outside the official WordPress.org repository or a legitimate vendor frequently ship with a backdoor already baked in.
  • Insecure file permissions. Overly permissive permissions on wp-content/uploads or theme/plugin directories can let an attacker who's gained any foothold write executable PHP files where they shouldn't be able to.
  • Exposed configuration files. A misconfigured server that serves wp-config.php as plain text instead of executing it hands over your database credentials and secret keys directly.

Everything below addresses one or more of these specific paths.

Step 1: Disable the In-Dashboard File Editor

By default, any administrator can edit theme and plugin PHP files directly from Appearance > Theme File Editor or Plugins > Plugin File Editor. If an attacker ever compromises an admin account (or an admin session, via a stolen cookie or an XSS vulnerability), this editor hands them a code execution point with zero extra effort. Disable it entirely by adding this line to wp-config.php, above the /* That's all, stop editing! */ comment:

define( 'DISALLOW_FILE_EDIT', true );

If you also want to prevent installing or updating plugins and themes from the dashboard (relevant for sites where deployments go through Git or a CI pipeline instead), you can go a step further:

define( 'DISALLOW_FILE_MODS', true );

Be aware that DISALLOW_FILE_MODS also disables automatic background updates for minor core releases, so only set it if your deployment process already handles updates another way.

Step 2: Lock Down Sensitive Files with .htaccess

On an Apache server, .htaccess rules run before WordPress ever loads, which makes them one of the most effective places to block access to files that should never be requested directly. Add this to your site's root .htaccess, above the # BEGIN WordPress block:

# Block all direct access to wp-config.php
<Files wp-config.php>
Order allow,deny
Deny from all
</Files>

# Block XML-RPC requests (a common brute-force and DDoS amplification target)
<Files xmlrpc.php>
Order allow,deny
Deny from all
</Files>

# Disable directory browsing
Options -Indexes

# Block access to .htaccess itself and other hidden dotfiles
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>

wp-config.php holds your database credentials and secret authentication keys; it should never be reachable by a direct HTTP request under any server misconfiguration. xmlrpc.php is a legitimate WordPress feature (used by the mobile app and some integrations like Jetpack), but it's also one of the most abused endpoints on the platform, frequently used for brute-force login attempts via its system.multicall method and for DDoS amplification (pingback abuse) against other sites. If you don't rely on XML-RPC for anything, blocking it entirely closes off both.

Stop PHP From Executing Inside the Uploads Folder

The wp-content/uploads directory should only ever hold media, never executable code, but it's also one of the few directories that's typically writable by the web server, which makes it a favorite target for attackers who've found any way to upload a file. Create a .htaccess file inside wp-content/uploads/ (a separate file from your root one) containing:

<Files *.php>
Deny from all
</Files>

This means that even if malware somehow gets a PHP file written into your uploads folder, the server refuses to execute it, turning a potential full compromise into a mostly harmless, easily deleted file.

Step 3: Set Correct File and Directory Permissions

Permissions that are too permissive give an attacker who's gained any foothold room to modify more than they should; permissions that are too restrictive break normal WordPress functionality like automatic updates and media uploads. The generally recommended baseline is:

  • Directories: 755 (owner can read/write/execute, everyone else can read/execute but not write)
  • Files: 644 (owner can read/write, everyone else can only read)
  • wp-config.php: 440 or 400 where your hosting setup allows it, since this file needs to be readable only by the user PHP runs as, never writable by other accounts on a shared server

If you have SSH access, you can apply the standard directory/file split with two find commands run from your WordPress root:

find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;

Run these carefully and only from your actual WordPress root directory, and follow up by resetting wp-config.php to a stricter permission afterward if your host supports it, since the blanket 644 above will have reset it to a more permissive value than ideal.

Step 4: Add Security Headers

HTTP security headers tell the browser itself to enforce certain protections, adding a layer of defense that doesn't depend on WordPress, a plugin, or your server configuration catching every possible attack. Add this to your theme's functions.php, hooking into send_headers, which fires as WordPress is about to send its response:

add_action( 'send_headers', function () {
    header( 'X-Frame-Options: SAMEORIGIN' );
    header( 'X-Content-Type-Options: nosniff' );
    header( 'Referrer-Policy: strict-origin-when-cross-origin' );
    header( 'Permissions-Policy: geolocation=(), microphone=(), camera=()' );

    if ( is_ssl() ) {
        header( 'Strict-Transport-Security: max-age=63072000; includeSubDomains; preload' );
    }
} );

Here's what each header actually does:

  • X-Frame-Options: SAMEORIGIN stops your pages from being loaded inside an <iframe> on another domain, which prevents clickjacking attacks where an attacker overlays invisible buttons from your site (like a "delete account" button) on top of their own page.
  • X-Content-Type-Options: nosniff stops the browser from trying to guess a file's type based on its content rather than its declared Content-Type, which closes off a class of attack where a file disguised as an image gets executed as a script instead.
  • Referrer-Policy: strict-origin-when-cross-origin limits how much of your URL structure leaks to external sites when a visitor clicks an outbound link.
  • Permissions-Policy explicitly disables browser features your site doesn't use, reducing the surface a malicious script (from a compromised plugin or theme, for instance) could abuse.
  • Strict-Transport-Security, sent only when the request is already over HTTPS (is_ssl()), tells the browser to always use HTTPS for your domain going forward, even if a visitor later types http:// explicitly, closing off downgrade and SSL-stripping attacks.

A Content-Security-Policy header is worth adding too, but it needs to be tailored to the specific scripts, styles, and embeds your theme and plugins actually load, since an overly strict policy will silently break functionality. Test it in Content-Security-Policy-Report-Only mode first before enforcing it.

Step 5: Limit Login Attempts

Brute-force attacks work by trying large numbers of password guesses against your login endpoint, which only works if the endpoint lets them try indefinitely. WordPress core has no built-in rate limiting on login attempts, so this needs to be added, either via a plugin or your own logic built on real WordPress hooks. Here's a working, minimal version using wp_login_failed and the authenticate filter alongside transients:

add_action( 'wp_login_failed', function ( $username ) {
    $ip       = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
    $attempts = (int) get_transient( 'tw_login_attempts_' . $ip );

    set_transient( 'tw_login_attempts_' . $ip, $attempts + 1, 15 * MINUTE_IN_SECONDS );
} );

add_filter( 'authenticate', function ( $user ) {
    $ip       = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
    $attempts = (int) get_transient( 'tw_login_attempts_' . $ip );

    if ( $attempts >= 5 ) {
        return new WP_Error( 'too_many_attempts', __( 'Too many failed login attempts. Please try again in 15 minutes.' ) );
    }

    return $user;
}, 30 );

This tracks failed attempts per IP address using a WordPress transient (essentially a cached value with a built-in expiration, here set to 15 minutes), and blocks further authentication attempts once a threshold is hit. For production use, a dedicated plugin like Limit Login Attempts Reloaded or the login-protection features bundled into Wordfence is generally the better choice, since they add IP allow-listing, notification emails, and a management screen on top of the same underlying idea. Pair rate limiting with two-factor authentication wherever your admin accounts support it; a correct password alone shouldn't be enough to get in.

Step 6: Install a Security Plugin for Ongoing Protection

Hardening code closes off known attack paths, but it doesn't watch for new threats, scan existing files for injected malware, or alert you when something changes unexpectedly. That's what a dedicated security plugin is for. Two of the most established options:

  • Wordfence runs an application-layer firewall directly inside WordPress, checking every request against a continuously updated ruleset before it reaches your site's PHP. It also includes a malware scanner that checks core, theme, and plugin files against known-good checksums to catch unauthorized modifications, plus login security features like rate limiting and optional 2FA.
  • Sucuri takes more of a defense-in-depth approach, offering a DNS-level firewall (traffic gets filtered before it ever reaches your server, not just inside WordPress), malware scanning, and a professional cleanup service if your site does get infected. Its DNS-based firewall model also provides real DDoS mitigation, which an in-application firewall like Wordfence's free tier can't fully replicate.

Either plugin, configured and kept updated, catches a large share of the threats that hardening code alone can't, particularly zero-day vulnerabilities in plugins you're already running.

Step 7: Keep Reliable, Tested Backups

No amount of hardening makes a site unhackable, it only makes it a harder, less attractive target. A tested backup is what turns a successful attack from a catastrophe into an inconvenience. If you haven't already set one up, our guide on how to create a WordPress backup manually walks through exporting your database and files without relying on a plugin, worth knowing even if you also run an automated backup solution day to day, since it's the method that still works when a plugin-based backup system is exactly what got compromised.

Step 8: Monitor for Signs of Compromise

Even a well-hardened site benefits from active monitoring, since new vulnerabilities are disclosed constantly and hardening today doesn't guarantee safety against next month's exploit. Watch for:

  • Unexpected admin users appearing in Users > All Users that you didn't create.
  • New scheduled tasks or cron jobs you don't recognize (malware frequently uses WordPress's cron system to keep reinfecting a site after cleanup; see our guide on setting up automatic cron jobs in WordPress for how legitimate scheduled tasks are supposed to look).
  • Unfamiliar files in wp-content/uploads with .php extensions, which a properly configured uploads .htaccess (Step 2) should already be blocking from executing, but shouldn't be there at all.
  • Sudden spikes in outbound traffic or server resource usage, which often indicates a compromised site being used to send spam or participate in a botnet.
  • Search engine warnings or a "This site may be hacked" label appearing in Google Search Console, one of the more common ways site owners first learn about an infection.

A malware scanner like Wordfence or Sucuri SiteCheck automates most of this monitoring, but periodically checking these signs manually costs nothing and catches issues a scanner's ruleset hasn't been updated to recognize yet.

Frequently Asked Questions (FAQ) About WordPress Malware and Hacker Protection

Common signs include unexpected redirects for visitors (especially from search engine results), new admin users you didn't create, unfamiliar files in wp-content, a spike in outbound email or traffic, or a Google Search Console warning. Running a scan with Wordfence or Sucuri SiteCheck is the fastest way to get a definitive answer, since some malware is designed to stay invisible during a normal browsing session.

For most sites, disabling XML-RPC is safe and recommended. It's required if you use the WordPress mobile app to publish content or rely on certain Jetpack features, so check whether either applies before blocking it entirely; if you only need part of its functionality, some security plugins let you disable specific XML-RPC methods (like system.multicall, the one most abused for brute-force attempts) rather than the whole file.

No, they address different layers. Good hosting typically protects the server environment (isolating accounts, patching the OS, providing a network-level firewall), but it can't fix a vulnerable plugin you've installed, an admin account with a weak password, or a theme file editor left enabled. Both layers matter, and neither substitutes for the other.

Running both application firewalls simultaneously is generally unnecessary and can cause conflicts, since they both hook into the same request lifecycle. Pick one as your primary firewall and malware scanner; layering in a CDN-level or DNS-level firewall (which is part of what Sucuri and some CDN providers offer) alongside an application-layer plugin is a reasonable combination, but running two full application-layer security suites at once usually just adds overhead.

Take the site offline or into maintenance mode to stop further damage and prevent visitors from being served malicious content, then change every password (WordPress admin, hosting, database, FTP/SFTP) since credentials may already be compromised. Restore from a known-clean backup where possible, or use a malware removal tool or professional cleanup service, then apply the hardening steps in this guide before bringing the site back online so the same vulnerability doesn't let the attacker back in immediately.

SSL encrypts data in transit between the visitor's browser and your server, which prevents eavesdropping and certain man-in-the-middle attacks, but it does nothing to prevent malware from being uploaded through a vulnerable plugin or a compromised admin account. It's a necessary piece of a security posture, not a substitute for the hardening and monitoring steps covered here.

The free tiers of Wordfence and Sucuri's scanner cover a meaningful baseline: firewall rules, malware scanning, and basic login protection. Paid tiers typically add faster firewall rule updates (Wordfence's free tier delays new rules by 30 days), automated malware removal, and in Sucuri's case, a DNS-level firewall and guaranteed cleanup service. For a site handling sensitive data or meaningful revenue, the paid tier is usually worth it for the faster rule updates alone.

Conclusion

WordPress security isn't one setting you flip on, it's a set of overlapping layers, each closing off a different path an attacker could otherwise use. Disabling the file editor and locking down wp-config.php and xmlrpc.php addresses configuration weaknesses; correct file permissions and a blocked uploads folder limit what an attacker can do even if they find a foothold; security headers add browser-enforced protection that doesn't depend on WordPress at all; and a plugin like Wordfence or Sucuri watches continuously for the threats hardening alone can't anticipate.

None of these layers make a site unhackable, and treating any single one as a complete solution is itself a risk. What actually reduces real-world compromise rates is doing all of them together, consistently, and pairing them with the fundamentals covered in our broader guide on how to secure a WordPress website: regular updates, strong unique credentials, and backups you've actually tested restoring from.

Here are a few additional resources worth reading as you harden your own site further:

Tags :
Share :

Related Posts

Effortlessly Crafting Compelling WordPress Pages

Effortlessly Crafting Compelling WordPress Pages

As a website owner or content creator, having the ability to seamlessly add new pages to your WordPress site is crucial. Whether you're introducing a

Continue Reading
High Traffic Tips for WordPress Mastery 🚥

High Traffic Tips for WordPress Mastery 🚥

In our digital age, where online visibility is paramount, ensuring your WordPress site can handle surging traffic is crucial. Just like a finely-tune

Continue Reading
How Do I Change the WordPress Login URL?

How Do I Change the WordPress Login URL?

By default, every WordPress site's login page lives at the same predictable address: yoursite.com/wp-login.php (which also happens to redirect from

Continue Reading