In this guide: 9 chapters
Most WordPress hacks do not start with a clever attack on WordPress itself. They start with an old plugin, a reused password or a file that should never have been writable. That is good news, because each of those has a fix you can make today.
This guide covers the fixes in order of impact. Chapter 1 shows where attacks come from. Chapters 2 to 6 close the common ways in, Chapters 7 and 8 make sure you can recover and notice problems fast, and Chapter 9 is the plan for the day something goes wrong. Read that one before you need it.
Why WordPress sites get hacked
Patchstack tracks security bugs across the WordPress world. Its report on 2025, published in 2026, shows where the real risk sits:
- 11,334new security bugs found in 2025, 42% more than the year before
- 91%were in plugins and 9% in themes. WordPress core had 6, all low priority.
- 46%had no fix from the developer when the bug was made public
- 5 hoursmedian time until attacks began on the most heavily exploited bugs
- 12% to 26%of attacks blocked by standard hosting defenses in Patchstack’s tests
- 76%of bugs in premium plugins could be used in real attacks
The weak spot is plugins and themes, and attacks can start within hours of a bug going public. A monthly update routine is too slow on its own. You need fast updates, fewer plugins and a firewall that can block known attacks while you wait for a fix.
What attackers want
Most attacks are automated and have nothing to do with you personally. Bots scan millions of sites for one known bug, then use every site they break into. Hacked WordPress sites are used to publish spam pages that ride on your Google ranking, send visitors to scam sites, steal card numbers from checkout pages, send spam email and attack other servers. A small business site is just as useful to them as a big one.
The other common ways in
- Stolen or reused passwords. Bots try leaked passwords against
wp-login.phpall day. - Nulled plugins and themes. Pirated copies of paid plugins often ship with a backdoor already inside.
- File manager plugins. They let anyone with an admin login, or a stolen admin session, upload and edit any file on the server. In 2026 we traced backdoors on a hacked site back to one, and we now block that kind of plugin on every site we host.
- Forgotten installs. Old staging copies and test sites on the same account still run old code, and one infected site can spread to the others.
- Old PHP and old servers. Versions past end of life stop getting security fixes.
Start by taking stock of what you run:
wp plugin list --fields=name,status,version,update_version
wp theme list --fields=name,status,version,update_versionYou have a list of every plugin and theme, you know which ones you actually use, and none of them came from a download site instead of the developer.
Lock down logins and users
A stolen admin login gives an attacker everything. These steps make logins hard to guess, hard to reuse and easy to spot when something is off.
- Use unique admin usernames. Never use
adminor the site name. WordPress cannot rename a user, so create a new administrator, log in as that user, then delete the old one and give its posts to the new account. - Use strong, unique passwords. A password manager makes this easy. Every person gets their own login. Shared logins make it impossible to tell who did what.
- Turn on two-factor authentication or passkeys for every administrator, editor and shop manager. The free Two-Factor plugin from WordPress contributors is a good start.
- Limit login attempts. Block or slow down an IP address after a few failed logins, and add bot protection such as Cloudflare Turnstile to login and contact forms.
- Give people the lowest role that works. Writers get Author or Editor. Store staff get Shop Manager. Only the people who manage plugins and settings need Administrator.
- Remove old users. Former staff, old agencies and one-time contractors should not keep access.
- Review application passwords. WordPress lets apps log in with special passwords listed under Users > Profile > Application Passwords. Remove any you do not recognize.
# Who has admin access?
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
# Replace a user named "admin" (use the new user's ID in --reassign)
wp user create sitemanager you@example.com --role=administrator
wp user delete admin --reassign=2
# App passwords for one user
wp user application-password list sitemanagerStop author scans
Visiting /?author=1 makes WordPress redirect to the author’s page, which reveals the username. The public REST API can list authors too. These two snippets close both, without affecting logged-in users:
// Redirect ?author=N scans to the home page
add_action( 'template_redirect', function () {
if ( ! is_admin() && isset( $_GET['author'] ) ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
} );
// Hide the user list from the REST API for visitors
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
} );Passkeys and two-factor logins
A two-factor login asks for a second proof, usually a six-digit code from an app on your phone, after the password. A passkey replaces the password with a key stored on your phone or computer and unlocked with your fingerprint, face or PIN. Both stop a leaked password from being enough. Avoid text message codes where you can, because phone numbers can be hijacked.
Protect the accounts around WordPress
Your site is only as safe as the accounts that control it. Turn on two-factor logins for your hosting account, your domain registrar, your DNS provider (such as Cloudflare) and the email account that receives your password resets. Someone who gets into your registrar can move your domain. Someone who gets into your email can reset every other password.
Every site we host runs our login limiter, and Cloudflare’s firewall filters bot logins before they reach WordPress. Our Security Hardening service audits every admin user and credential on the site.
The administrator list holds only people who need it, no account is named admin, every admin uses 2FA, and https://example.com/?author=1 no longer shows a username.
Keep updates fast and plugins few
Most hacks use a bug that already has a fix. The site was just not updated in time. With attacks starting within hours of some bugs going public, speed matters.
- Let minor core updates run automatically. WordPress does this by default. Leave it on.
- Turn on auto-updates for plugins you trust. Each plugin has an “Enable auto-updates” link on the Plugins screen.
- Test big updates on staging first. Page builders, WooCommerce and theme updates can change how the site looks or works. Copy to staging, update, click through, then update live.
- Delete plugins you do not use. A deactivated plugin’s files are still on the server and can still be attacked. Deactivating is not enough.
- Replace abandoned plugins. Check the “Last updated” date on WordPress.org. If a plugin has not been updated in over a year, or WordPress.org has closed it, find a replacement.
- Never use nulled plugins or themes. Buy from the developer, and keep licenses active so you get security fixes.
# What needs updating?
wp plugin list --update=available
wp theme list --update=available
# Remove inactive plugins you no longer need
wp plugin list --status=inactive
wp plugin delete plugin-slug
# Update everything, then confirm core files are clean
wp core update && wp plugin update --all && wp theme update --all
wp core verify-checksumsWatch for security alerts
Sign up for vulnerability alerts that cover the plugins you use. The Patchstack and Wordfence vulnerability databases are free to search, and several services email you when a plugin on your list gets a new bug. When an alert names a plugin you run, update it that day or turn it off until a fix ships.
Premium plugins need extra care
Patchstack counted three times as many known exploited bugs in premium plugins and themes as in free ones. Premium plugins often update outside the normal WordPress screen, so an expired license means you stop getting fixes without noticing. Keep a list of every paid plugin with its renewal date.
Virtual patching
Nearly half of new bugs had no fix when they were made public. A web application firewall with virtual patching blocks the attack pattern for a known bug before the developer ships a fix. Services such as Patchstack and the managed rules in Cloudflare’s WAF work this way.
A weekly update routine
- Pick a set time each week, early in a quiet day.
- Copy the live site to staging and run the updates there.
- Click through the home page, a contact form, the cart and checkout, and the admin.
- Update the live site, then repeat the same quick test.
- Write the date and what changed in your change log, so a problem can be traced back to an update.
We update WordPress core and plugins every week on every plan, push urgent security releases right away, and run Imunify360’s firewall and malware scanner on every server.
wp plugin list --update=available returns nothing, there are no inactive plugins left, and every premium plugin has an active license.
Harden WordPress with a few lines of config
These settings close common doors attackers use once they find one weakness. Most take a minute. Make a backup of wp-config.php and .htaccess first, then add the changes one at a time and reload the site after each.
In wp-config.php
// Turn off the theme and plugin file editor in the dashboard
define( 'DISALLOW_FILE_EDIT', true );
// Load admin scripts one by one instead of combined through
// load-scripts.php (one of WP Toolkit's hardening checks)
define( 'CONCATENATE_SCRIPTS', false );Rotate your security keys. The keys (often called salts) sign login cookies. New keys log everyone out, which is exactly what you want after a staff change or a scare:
wp config shuffle-saltsTurn off debug output on the live site. Error messages show file paths and plugin names to anyone who triggers them. If you need a log, write it to a file outside the web folder:
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_DISPLAY', false );
// Only while you are fixing something:
// define( 'WP_DEBUG', true );
// define( 'WP_DEBUG_LOG', '/home/user/logs/wp-debug.log' );A debug.log left in wp-content is readable by anyone who guesses the address. Delete it when you are done, or block it with the sensitive files rule below.
Use HTTPS everywhere. Make sure every page redirects from http:// to https://, and that both the WordPress Address and Site Address under Settings > General start with https://.
Table prefix. WordPress’s hardening guide notes that a prefix other than wp_ blocks some automated SQL injection attacks. Pick a custom prefix on every new install. On an existing site, changing it is risky and the gain is small, so only do it during a rebuild.
File permissions
Folders should be 755 and files 644. wp-config.php should be 400 or 440, so only you and the web server can read it. On hosts where PHP runs as your own account user, 600 also works. Run these from the WordPress folder as the site’s own user, never as root:
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 600 wp-config.phpBlock PHP where it never belongs
Uploads and cache folders hold images and generated files. They should never run PHP. A backdoor uploaded there is harmless if the server refuses to run it. On Apache, put this .htaccess file in wp-content/uploads/ and in your cache folder:
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>The WordPress hardening guide also blocks direct access to PHP files in wp-includes. Add this to the main .htaccess, outside the # BEGIN WordPress block. Skip the third rule on Multisite, where it breaks images served by ms-files.php:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>On NGINX, the same idea looks like this:
location ~* /wp-content/(uploads|cache)/.*\.(php|phtml|phar)$ { deny all; }
location ~* ^/wp-includes/[^/]+\.php$ { deny all; }Block sensitive files, XML-RPC and folder listings
# No folder listings
Options -Indexes
# Config, server files and leftovers
<Files wp-config.php>
Require all denied
</Files>
<FilesMatch "^(\.htaccess|\.htpasswd|\.user\.ini|readme\.html|license\.txt|wp-config-sample\.php|debug\.log)$">
Require all denied
</FilesMatch>
<FilesMatch "\.(sql|bak|old|orig|swp|log)$">
Require all denied
</FilesMatch>
# XML-RPC (remove this block if you use Jetpack or the WordPress mobile app)
<Files xmlrpc.php>
Require all denied
</Files>Blocking xmlrpc.php also turns off pingbacks, which attackers use to flood other sites. If you need XML-RPC for an app, turn off pingbacks alone under Settings > Discussion by unticking “Allow link notifications from other blogs”.
cPanel and Plesk include WP Toolkit, whose Security screen can apply most of these settings for you. Pick the measures one by one. Applying all of them at once also turns on hotlink protection, which can break images shown in emails, feeds and other sites.
We apply these settings with WP Toolkit on every site we host, set wp-config.php to 600, and block file manager plugins across the whole network with our own Plugin Guard.
Open these in a browser: /xmlrpc.php, /wp-config.php, /readme.html and /wp-content/uploads/. Each should show 403 Forbidden or a blank page with no file list.
In the dashboard, Appearance > Theme File Editor should be gone.
Add security headers
Security headers are short instructions your server sends with every page. They tell the browser to always use HTTPS, refuse to be framed by other sites and block some kinds of injected scripts. They take minutes to add and they protect every visitor.
| Header | What it does | Starter value |
|---|---|---|
| Strict-Transport-Security | Forces HTTPS on every visit after the first | max-age=31536000; includeSubDomains |
| Content-Security-Policy | Limits where scripts, styles and frames can load from | Start in report-only mode (see below) |
| X-Frame-Options | Stops other sites from framing yours (clickjacking) | SAMEORIGIN |
| X-Content-Type-Options | Stops the browser from guessing file types | nosniff |
| Referrer-Policy | Limits what your URLs reveal to other sites | strict-origin-when-cross-origin |
| Permissions-Policy | Turns off browser features you do not use | camera=(), microphone=(), geolocation=() |
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy-Report-Only "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
</IfModule>add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;Only add includeSubDomains once every subdomain works over HTTPS. Browsers remember HSTS for the whole max-age, so a mistake can lock visitors out of a subdomain for a year.
Content-Security-Policy can break a site. WordPress, page builders and tracking tools usually need 'unsafe-inline'. Run it in report-only mode first, watch the browser console for blocked items, then switch to the enforcing header.
If you use Cloudflare, you can turn on HSTS under SSL/TLS > Edge Certificates and add the other headers with a Response Header Transform Rule.
Our Security Hardening service sets these headers for your site and tests every page type, including checkout and logins, before they go live.
Run our free security test, which uses Mozilla’s HTTP Observatory. Aim for a B or better. Or check from a terminal:
curl -sI https://example.com/ | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
Protect the server and network
WordPress settings only go so far. Many attacks are best stopped before they reach WordPress at all, at the network edge or on the server. These are the layers to ask your host about, or to set up if you run your own server.
- A web application firewall (WAF). It blocks known attack patterns, bad bots and floods of login attempts. Cloudflare’s WAF works at the edge. Server WAFs such as Imunify360 or ModSecurity catch what gets through.
- Bot protection and rate limits. Challenge bots on
/wp-login.phpand/xmlrpc.php, and rate-limit repeated POST requests to them. - Server-side malware scanning. A scanner on the server sees every file, including ones a WordPress plugin cannot reach.
- Live kernel patching. Tools such as KernelCare patch the Linux kernel without a reboot, so security fixes do not wait for a maintenance window.
- Isolation between sites. One hacked site should not be able to read or change another. Keep each site under its own account where you can, and delete old staging and test copies.
- SSH keys over passwords. Use SFTP or SSH with keys. Turn off plain FTP.
- No file manager plugins. Use SFTP or your host’s own file manager instead.
- Current PHP and database versions. Versions past end of life stop getting security fixes. WordPress recommends PHP 8.3 or newer and MySQL 8.0 or MariaDB 10.11 or newer.
In Cloudflare, a simple custom rule with a Managed Challenge action stops most login bots before they reach your server:
(http.request.uri.path eq "/wp-login.php" and http.request.method eq "POST")
or (http.request.uri.path eq "/xmlrpc.php")Every server runs Imunify360 for firewall and malware scanning and KernelCare for live kernel patches, behind Cloudflare’s WAF. Our Plugin Guard blocks file manager plugins on every site.
Your host can name the firewall and malware scanner that protect your account, SFTP and SSH logins use keys, and no file manager plugin is installed.
Back up so you can always recover
Backups are your last line of defense. When everything else fails, a clean backup turns a disaster into an afternoon. A backup only counts if it is off-site, old enough and tested.
Follow the 3-2-1 rule
- 3 copies of your site: the live site plus two backups.
- 2 kinds of storage, such as your host’s backup system and a separate cloud storage bucket.
- 1 copy off-site, with its own login, so an attacker who gets into your hosting account cannot delete it.
Keep enough history
Infections are often found weeks after they start. If you only keep 7 days of backups, every copy may already be infected. Keep at least 30 daily restore points plus a few monthly copies. Storage that supports versioning or object lock stops anyone, including an attacker, from changing old copies.
Back up the right things
You need the database and the wp-content folder, plus wp-config.php. WordPress core files can be downloaded again. If your email is hosted on the same account, back that up too.
# Database and files
wp db export site-$(date +%F).sql
tar -czf site-files-$(date +%F).tar.gz wp-content wp-config.php
# Copy off-site with rclone (set up the "offsite" remote first)
rclone copy . offsite:my-site-backups/$(date +%F) --include "site-*"Test your restores
A manual restore from those two files looks like this. Most hosts also offer one-click restores, which are faster and safer when you are under pressure:
tar -xzf site-files-2026-10-01.tar.gz
wp db import site-2026-10-01.sql
wp cache flushEvery quarter, restore a backup to a staging site and click through the home page, a form and the admin. A backup that has never been restored is a guess.
Every plan includes daily off-site backups with JetBackup to Wasabi, 30 daily restore points plus 3 monthly, and free restores by our team.
You restored a backup to staging in the last 90 days, the backup lives outside your hosting account, and you know the date of your oldest restore point.
Monitor so you notice problems fast
The sooner you notice a problem, the smaller it is. Set up a few alerts so you hear about trouble before your visitors or Google do.
- Uptime monitoring. Checks the site every few minutes and alerts you if it goes down or shows an error. Our uptime monitor page explains how ours works.
- An activity log. A plugin such as WP Activity Log or Simple History records logins, new users, plugin installs and setting changes.
- File integrity checks. WP-CLI compares your core files, and plugins from WordPress.org, with the official copies.
- Google Search Console. Turn on email alerts. The Security Issues report tells you if Google finds malware or hacked content on your site.
wp core verify-checksums
wp plugin verify-checksums --all
wp user list --role=administrator --fields=ID,user_login,user_registeredYou can also check your site from the outside. Google’s Safe Browsing site status page shows whether Google currently flags your domain as unsafe.
Warning signs
- A new administrator you did not add, or a plugin you did not install.
- Visitors on phones get sent to another site, while the site looks normal on your computer.
- Spam pages in Google. Search
site:example.comwith words like casino, pills or loans. - Changes to
.htaccess, new files inwp-content/uploadsending in.php, or a sudden slowdown. - Your host suspends the account or Google shows a warning before your site loads.
Uptime monitoring and server-side malware scanning run on every plan, and alerts go straight to our team.
You get an alert within minutes if the site goes down, the checksum checks pass, and Search Console email alerts are turned on.
If you are hacked: a step-by-step recovery plan
If you see warning signs, work through these steps in order. Rushing to delete files usually destroys the evidence you need to find out how the attacker got in.
- Contain it. If the site is sending visitors to malware or stealing payments, take it offline or turn on maintenance mode. Tell your host.
- Make a copy for forensics. Download the files, the database and the server access logs before you change anything. The logs often show exactly how the attacker got in.
- Change every password. WordPress admins, hosting and cPanel, SFTP and SSH, the database password (then update
wp-config.php), email accounts tied to the site and API keys for payment and email services. Then runwp config shuffle-saltsto log everyone out. - Replace code from clean sources. Reinstall WordPress core, plugins and themes from WordPress.org or the developer. Delete anything you do not recognize.
- Hunt for backdoors. Attackers almost always leave more than one way back in. Look for PHP files in uploads, recently changed files and code that hides what it does.
- Check the database. Look for unknown admins, a changed site URL, scripts injected into posts and widgets, and strange scheduled tasks.
- Restore if it is widespread. If the infection is everywhere, restore a backup from before it started, then update everything before you go live again.
- Ask Google for a review. Once the site is clean, open the Security Issues report in Search Console and click Request Review.
- Close the way in. Find the old plugin, weak password or exposed file the attacker used and fix it, or the same thing will happen again.
wp core download --force --skip-content
wp plugin install plugin-slug --force
wp core verify-checksums# PHP files where only images belong
find wp-content/uploads -name "*.php"
# PHP files changed in the last 14 days, newest first
find . -name "*.php" -mtime -14 -printf "%TY-%Tm-%Td %p\n" | sort -r | head -50
# Code that hides what it does (good plugins use some of these too, so check each hit)
grep -rlE "eval\(|base64_decode\(|gzinflate\(|str_rot13\(" wp-content --include=*.phpwp user list --role=administrator
wp option get siteurl
wp option get home
wp db query "SELECT ID, post_title FROM $(wp db prefix)posts WHERE post_content LIKE '%<script%' OR post_content LIKE '%<iframe%';"
wp cron event listDeleting the files a scanner flags is rarely enough. Most reinfections come from a second backdoor that was missed or from a way in that was never closed.
If customer data may be exposed
If the attacker could have reached customer accounts, order details or card data, you may have to tell your customers and your payment processor. The rules depend on your state, your country and what data was involved, so talk to a lawyer early. Keep the forensic copy from step 2; it shows what the attacker could reach.
Hack cleanup and malware removal are included on every plan, with 60 days of monitoring for reinfection.
Not hosted with us? Our emergency team fixes hacked sites for the cost of a month of hosting.
Checksums pass, no unknown admins or files remain, Search Console shows no security issues, and you know how the attacker got in.
Written and reviewed by Nicky Stafford, founder of WP Farm. WP Farm has run managed WordPress hosting since 2008. Our team handles hack cleanups and malware removal for our customers, and every step in this guide is one we apply to the sites we host.