security
Hardening WordPress: A Developer's Security Checklist
Published 2026-09-09 · 7 min read
WordPress powers a huge share of the web, which makes it the most automated target on the internet. The good news for developers: the ways WordPress sites get compromised are well understood, and almost all of them are preventable with configuration and coding discipline. This is a technical checklist for developers and agencies who build and maintain WordPress sites: how the attacks work, and exactly how to shut each one down.
The examples assume Apache/LiteSpeed hosting (so .htaccessA per-directory Apache/LiteSpeed config file. It can change how files in that folder are served — including whether they run as code. applies), which is
what most cPanelA popular web-hosting control panel for managing sites, email, databases, and files through the browser. environments use, with nginx equivalents where relevant.
How WordPress sites actually get hacked
Three vectors account for most real-world compromises:
- Vulnerable plugins and themes. This is by far the biggest. Patchstack's 2026 report found that of the new WordPress vulnerabilities disclosed in 2025, 91% were in plugins, not core. Attackers scan the web for known-vulnerable versions and exploit them automatically, often within hours of a public disclosure. Abandoned plugins that no longer receive updates are especially dangerous, and supply-chain attacks (where a legitimate plugin is bought or hijacked and backdoored) have hit dozens of popular plugins.
- Weak or reused credentials. Brute-forceAn attack that rapidly tries many username and password combinations until one works. and credential-stuffing attacks
against
wp-login.phpWordPress's login page — the constant target of automated brute-force and credential-stuffing attempts. are constant background noise. - Insecure custom upload code. A media or attachment feature in a custom plugin or theme that doesn't validate strictly is the same webshellA small malicious script an attacker uploads to run commands on your server through the browser — the usual foothold for hosting scam pages. risk as any PHP upload form.
Once in, attackers monetize the foothold by adding scam and spam pages
(fake shops, phishingFraudulent pages or messages that impersonate a trusted brand to trick people into giving up credentials or payment details., pharma/SEO spam), frequently cloaked so it's served only
to search engines. They also plant persistence: hidden admin accounts, modified
core files, or mu-plugins that silently reinstall the malwareMalicious software planted on a site or server, often to steal data, send spam, or host scam pages., which is why
poorly cleaned sites get reinfected. Break the entry points below and none of
that gets started.
1. Lock down wp-config.php
wp-config.phpWordPress's main configuration file. It holds the database credentials and secret keys, so it's a prime target to protect. holds your database credentials and security keys. Harden it:
// Use unique, random keys and salts:
// https://api.wordpress.org/secret-key/1.1/salt/
define('AUTH_KEY', '…'); // paste freshly generated values
// … the full set of 8 keys/salts …
// Block the built-in file editors (no code editing from wp-admin):
define('DISALLOW_FILE_EDIT', true);
// Optionally block plugin/theme installs and updates from the dashboard,
// so a compromised admin session can't add malicious code:
define('DISALLOW_FILE_MODS', true);
DISALLOW_FILE_EDITA wp-config.php setting that removes the built-in theme/plugin code editors from the dashboard, so a stolen admin session can't inject code there. removes the Appearance/Plugin editors, a favorite way for
attackers with a stolen admin sessionA way for a site to remember a specific visitor across multiple requests, usually backed by a cookie. to inject code. Fresh salts invalidate any
stolen session cookiesA small piece of data a site stores in your browser to remember things like your login between requests.. Where your host's layout allows it, moving wp-config.php
one level above the web root keeps it out of reach of direct requests.
Deny direct web access to it as a backstop (Apache):
<Files wp-config.php>
Require all denied
</Files>
2. Block PHP execution in wp-content/uploads
This is the highest-impact control for the scam-pages problem. The uploads directory should only ever contain media, never executable code. If a plugin flaw ever lets an attacker write a PHP file there, blocking execution makes it inert.
Create wp-content/uploads/.htaccess (Apache/LiteSpeed):
<FilesMatch "\.(php|phtml|php[0-9]|phar|pht)$">
Require all denied
</FilesMatch>
Or scope it in nginx:
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
Also block direct PHP execution where none should occur (for example inside
wp-includesA core WordPress folder of internal PHP files. Nothing should ever execute a PHP file you place here directly.), which several hardening guides and the official docs recommend.
3. Apply least privilege: permissions and roles
File permissions. Directories 755A Unix permission: full access for the owner, read and execute for everyone else. The normal setting for directories., files 644A Unix permission: read and write for the owner, read-only for everyone else. The normal setting for files., and wp-config.php
tightened further (640 or 600 where the host allows). Never 777A Unix permission granting read, write, and execute to everyone. Never use it on web files — any process could modify or run them. on
anything:
find /home/user/public_html -type d -exec chmod 755 {} \;
find /home/user/public_html -type f -exec chmod 644 {} \;
chmod 640 /home/user/public_html/wp-config.php
Files should be owned by the site's account, not by the web server user, so a compromised web process can't rewrite your code.
User roles. Give each person the lowest role that lets them do their job: most content editors need Editor or Author, not Administrator. Every extra admin account is another set of credentials worth stealing. Audit users regularly and remove ones you don't recognize (attackers create hidden admins).
4. Reduce the plugin/theme attack surface
Since plugins are the dominant vector, treat dependency hygiene as security work:
- Update on a schedule, and enable auto-updates for plugins where you can tolerate it. An unpatched, known-vulnerable plugin is the most likely way in.
- Remove, don't just deactivate, unused plugins and themes. Deactivated code still sits on disk and can still be exploited. Delete the default themes you aren't using too.
- Vet what you install. Prefer actively maintained plugins with a recent update date and a real support history. Be wary of plugins that changed ownership. Never install nulled/pirated premium plugins; they are a classic malware delivery method.
- Track vulnerability disclosures for the plugins you rely on so you can patch fast when something is announced.
5. Write secure upload and handler code
If you build custom plugins or themes that accept uploads or handle actions, use WordPress's own APIs rather than raw PHP; they carry the validation you'd otherwise have to reimplement:
add_action('admin_post_my_upload', function () {
// 1. Verify the request came from your form
check_admin_referer('my_upload_action', 'my_upload_nonce');
// 2. Verify the user is allowed to do this
if (!current_user_can('upload_files')) {
wp_die('Insufficient permissions.', 403);
}
// 3. Validate the file against a whitelist by content + extension
$file = $_FILES['my_file'];
$check = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
$allowed = ['jpg' => 'image/jpeg', 'png' => 'image/png', 'pdf' => 'application/pdf'];
if (!$check['ext'] || !in_array($check['type'], $allowed, true)) {
wp_die('File type not allowed.', 422);
}
// 4. Let WordPress store it safely in the managed uploads dir
$result = wp_handle_upload($file, ['test_form' => false]);
if (isset($result['error'])) {
wp_die(esc_html($result['error']));
}
// $result['url'] / $result['file'] are now safe to use
});
The four non-negotiables: a nonce (check_admin_refererA WordPress function that verifies a security token (nonce) so a request genuinely came from your own form.) to stop CSRFCross-Site Request Forgery — tricking a logged-in user's browser into submitting an action they didn't intend., a
capability check (current_user_canA WordPress capability check — it confirms the logged-in user is actually allowed to perform an action.) so only authorized users act,
content-based validation (wp_check_filetype_and_extA WordPress function that validates an uploaded file's real type and extension against an allowed list. against a whitelist),
and wp_handle_uploadThe WordPress function that stores an uploaded file safely in the managed uploads directory, applying WordPress's own checks. so files go through WordPress's managed path instead of
raw move_uploaded_fileThe raw PHP function that moves an uploaded file into place. Safe only if you've already validated and renamed the file yourself.. And as always: escape output (esc_html, esc_attr,
esc_url), and sanitize and parameterize anything that touches the database
($wpdb->prepare) to close XSSCross-Site Scripting — an attack that injects malicious scripts into a page to run in visitors' browsers. and SQL injectionAn attack that smuggles malicious database commands through an input the site failed to sanitize. alongside uploads.
6. Detect and respond
Assume something will eventually try to get in, and make sure you'd notice:
- File-integrity monitoring (WordPress's own core checksums, or a security plugin) will flag modified core files, a common malware tactic.
- A security plugin for firewalling and malware scanning. See Wordfence and other WordPress security plugins for how the main options compare. Treat it as a layer on top of the basics above, not a substitute.
- Reliable, off-site backups, tested by actually restoring one. A clean backup is the fastest route out of a compromise.
- Server-side scanning. cPanel hosting typically includes malware scanning and ModSecurityA web application firewall available on most cPanel hosting. It blocks many known attack patterns before they reach your code.; let them run. They're a safety net around good practice.
If a site does get hit, work through how to recover a hacked WordPress site in order, and be sure to find the entry point, or it will just happen again.
The developer hardening checklist
- Unique keys/salts in
wp-config.php;DISALLOW_FILE_EDITset -
wp-config.phpdenied direct access (and moved above web root if possible) - PHP execution blocked in
wp-content/uploadsThe folder where WordPress stores uploaded media. It should only ever hold files, never runnable code. - Permissions: dirs
755, files644, config tighter; correct ownership; no777 - Least-privilege user roles; no unknown admins
- Plugins/themes updated; unused ones deleted; sources vetted; no nulled plugins
- Custom code uses nonces, capability checks,
wp_check_filetype_and_ext,wp_handle_upload, and escapes/sanitizes everywhere - File-integrity monitoring, a security plugin, and tested off-site backups in place
None of this depends on a "more secure" host, these are application-level habits that keep a WordPress site out of trouble on any well-run platform. The developer companion on the raw PHP side is securing PHP file uploads, and the most common website and server attacks covers the broader threat landscape.
FAQs
How do most WordPress sites actually get hacked?+
Overwhelmingly through vulnerable or abandoned plugins and themes, not through WordPress core. Security firm Patchstack reported that 91% of new WordPress vulnerabilities disclosed in 2025 were in plugins. The other major vectors are weak or reused credentials and insecure custom upload code. Attackers automate scanning for known-vulnerable versions, so an out-of-date plugin can be exploited within hours of a disclosure.
Why does a hacked WordPress site end up hosting scam or spam pages?+
Once attackers gain a foothold, monetizing it usually means adding hidden pages: fake stores, phishing forms, pharma or SEO spam. Many campaigns cloak this content so it only shows to search engine crawlers, which is why a site can rank for spam terms while looking normal to its owner. Some malware also creates hidden admin accounts and reinfects the site after cleanup.
Is installing a security plugin enough to protect a WordPress site?+
No. A security plugin like Wordfence is a useful detection and firewall layer, but it runs inside the same WordPress that may be compromised, and it can't fix insecure configuration or bad custom code. Real hardening happens at the configuration, permissions, and code level; a plugin complements those basics rather than replacing them.
Should I block PHP execution in wp-content/uploads?+
Yes, for almost every site. WordPress uploads should only ever be media and documents, never scripts. Blocking PHP execution in wp-content/uploads means that even if an attacker manages to write a malicious PHP file there through a plugin flaw, it can't be run, which neutralizes one of the most common paths to a webshell.
What's the safest way to handle file uploads in a custom plugin or theme?+
Use WordPress's own APIs rather than raw PHP: verify a nonce, check the user's capability with current_user_can(), validate the file with wp_check_filetype_and_ext() against a whitelist, and store it with wp_handle_upload() so it lands in the managed uploads directory. Never move raw $_FILES data yourself, and never trust the client-supplied filename or MIME type.
Related reading
Securing PHP File Uploads: Stop Webshells and Scam Pages
How attackers turn PHP upload forms into webshells and scam pages, and the developer-side defenses that stop them: content validation, blocking execution, safe storage, and permissions.
2026-09-09 · 8 min read
WordPress Security Best Practices: A Complete Checklist
The practical, non-technical checklist for keeping a WordPress site secure: updates, passwords, backups, and the habits that actually prevent most hacks.
2026-08-27 · 3 min read
The WordPress Launch Checklist: Everything to Check Before Going Live
A complete, practical checklist for a finished WordPress site: domain and SSL, security, backups, SEO basics, and the small details that are easy to miss before launch.
2026-08-27 · 4 min read