WordPress Security Checklist (2026)

A comprehensive checklist for securing a WordPress project: from core configuration to the network level.

WordPress Core: updates and hardening

Updates

  • Always keep WordPress up to date — old versions no longer receive security patches.
  • Enable automatic updates for minor core versions.
  • Before major updates, make a backup and test on a staging environment.
  • Download the core exclusively from wordpress.org. No nulled or modified builds.
  • Regularly verify the integrity of core files with WP-CLI: wp core verify-checksums.
  • Remove files from the site root that reveal the WP version and other details: readme.html, license.txt, wp-config-sample.php. They are restored on core updates — automate their removal via a deploy script or cron.

wp-config.php configuration

Disabling file editing through the admin panel — protection against RCE; it disables the Theme Editor and Plugin Editor at once:

define('DISALLOW_FILE_EDIT', true);

Blocking plugin/theme installation and updates — only for production with CI/CD:

define('DISALLOW_FILE_MODS', true);

Forcing SSL for the admin panel:

define('FORCE_SSL_ADMIN', true);

Disabling the built-in wp-cron — replace it with a system cron:

define('DISABLE_WP_CRON', true);
System cron: */15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Disabling debug mode in production:

define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);

Unique security keys (Salts)

  • Generate them with the WordPress.org Salt Generator.
  • Updating the keys “resets” all active sessions.
  • Rotate the keys after any security incident.

Protecting wp-config.php

  • Move the file one level above the web server’s root directory.
  • Permissions: 600 or 640.
  • Block access at the web server level (see the “Server environment” section).

Themes and plugins

Choice and source

  • Install only from the official WordPress.org catalog or trusted marketplaces.
  • Before installing, assess: date of the last update, rating, number of active installations, reviews (especially regarding security).
  • Avoid “pirated” (nulled) versions — they often contain backdoors.
~97% of WordPress security issues are related specifically to plugins (data from Patchstack).

Updates

  • Regularly update all themes and plugins.
  • Disable and remove unused modules — even a deactivated plugin can be vulnerable.
  • Subscribe to notifications about critical updates from developers and vulnerability databases (Patchstack, WPScan).

Audit and verification

  • Scan plugins/themes for vulnerabilities and malicious code: WPScan, Sucuri SiteCheck, Patchstack, Wordfence.
  • For paid themes/plugins — check whether the developer has performed a security audit.
  • Test new extensions first on a local or staging server.
  • Check external dependencies (Composer, npm) for updates and CVEs.

Supply Chain Security

  • Use Subresource Integrity (SRI) for external scripts and styles.
  • Verify the checksums of downloaded packages.
  • Keep an eye on supply chain attacks through CVE databases.

Server environment

PHP

  • Use PHP 8.3 or 8.4 (the standard for 2026). Versions below 8.1 are a risk.
  • Hide the PHP version: expose_php = Off.
  • Configure open_basedir to restrict PHP’s access to the file system.

Disable dangerous functions in php.ini:

disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_multi_exec, parse_ini_file, show_source

File permissions

Object Permissions
WordPress files 644 (or 640)
Directories 755 (or 750)
wp-config.php 600
The file owner is a system user (not root).

Web server configuration

Disabling directory listing:

  • Apache: Options -Indexes in .htaccess.
  • Nginx: autoindex off;.

Blocking PHP execution in uploads:

Apache (.htaccess in wp-content/uploads/):

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

Nginx:

location ~* /wp-content/uploads/.*\.php$ {
deny all;
}

Blocking PHP execution in wp-includes:

Nginx:

location ~* /wp-includes/.*\.php$ {
deny all;
}

Apache (.htaccess in wp-includes/):

<FilesMatch "\.php$">
Require all denied
</FilesMatch>
Exception: wp-includes/ms-files.php is needed for Multisite.

Blocking access to system files:

Apache:

<FilesMatch "^(wp-config\.php|\.htaccess|\.git|\.env|\.svn|readme\.html|license\.txt)">
Require all denied
</FilesMatch>

Nginx:

location ~ /(\.|wp-config\.php|readme\.html|license\.txt) {
deny all;
}

Blocking xmlrpc.php at the server level:

Apache:

<Files xmlrpc.php>
Require all denied
</Files>

Nginx:

location = /xmlrpc.php {
deny all;
}

Security Headers

Configure them through the web server config or .htaccess:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Permitted-Cross-Domain-Policies: none
Content-Security-Policy: [configure carefully for the specific project]

Blocking indexing of the admin panel

Add X-Robots-Tag: noindex, nofollow for /wp-admin/ and /wp-login.php so search engines do not index service pages:

Nginx:

location ~* ^/(wp-admin|wp-login\.php) {
add_header X-Robots-Tag "noindex, nofollow" always;
}

SSL/TLS

  • Install an SSL certificate (Let’s Encrypt or a commercial one).
  • Redirect all HTTP traffic to HTTPS.
  • Enable HSTS.

SSH

  • Use SSH with keys (not a password).
  • Change the default SSH port.
  • Use Fail2Ban to block suspicious connection attempts.

Limiting request size

Protection against DoS via large uploads. Set a limit at the web server level:

  • Nginx: client_max_body_size 10m;
  • Apache: LimitRequestBody 10485760
Keep it consistent with upload_max_filesize and post_max_size in php.ini.

Filtering HTTP methods

Allow only GET, POST, HEAD for public pages. Block TRACE, DELETE, PUT, OPTIONS (if they are not needed for the REST API):

Nginx:

if ($request_method !~ ^(GET|POST|HEAD)$) {
return 405;
}

Rate Limiting at the server level

Separately from plugins, set up rate limiting for wp-login.php and xmlrpc.php:

Nginx:

limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
location = /wp-login.php {
limit_req zone=login burst=3 nodelay;
include fastcgi_params;
fastcgi_pass php;
}

Protection against hot-linking

Blocking direct embedding of images from other sites (bandwidth theft):

Nginx:

location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
valid_referers none blocked server_names *.yourdomain.com;
if ($invalid_referer) {
return 403;
}
}

Database

Configuration

  • Change the default wp_ prefix to a random one (for example, x7z_) at the installation stage.
  • Restrict the DB user’s privileges: only SELECT, INSERT, UPDATE, DELETE. No DROP, ALTER, CREATE, GRANT (if that is possible in the running mode).
  • Restrict remote access to MySQL: bind-address = 127.0.0.1 in the configuration.
  • Regular rotation of the DB password.
  • Use a separate DB user for each site on the server.

Authentication and access control

User registration

If the site does not need public registration — disable it: Settings → General → uncheck “Anyone can register”.

A trivial item, but often forgotten — open registration gives an attacker authenticated access to the REST API and internal endpoints.

Passwords

  • At least 12 characters: uppercase/lowercase letters, digits, special characters.
  • Check passwords against the HaveIBeenPwned database.
  • Use a password manager.
  • Regular rotation of passwords.

Two-factor authentication (2FA)

  • Mandatory for all administrators and editors.
  • SMS is legacy. Use:
    • Passkeys (biometrics, FIDO2) — the best option.
    • TOTP (Google Authenticator, Authy) — a good option.
  • Plugins: WP 2FA, Solid Security.

Protecting the login form

  • Limiting login attempts: block the IP after 3–5 failed attempts for 10–20 minutes (Limit Login Attempts Reloaded or an equivalent).
  • CAPTCHA (reCAPTCHA) on the login form and public forms.
  • Changing the login URL from /wp-admin to a custom one (security through obscurity, but an effective filter for bot noise).
  • Where possible — restrict access to the login page by IP through .htaccess or a firewall.

Application Passwords

For REST API integrations (n8n, Zapier, etc.) use Application Passwords instead of the admin’s real password.

The principle of least privilege

  • Grant the administrator role only to trusted users.
  • Give everyone else the minimally necessary roles.
  • Do not write content under the administrator account — create a separate “Author” or “Editor” account for publications. The admin account should not be the author of any post.
  • Regularly review the list of users and delete unnecessary accounts.
  • Do not leave the login admin or administrator.

Administrator User IDs

  • Delete or downgrade the role of users with IDs 1–10 (bots and brute-force scripts attack these IDs first).
  • Create a working admin account with an unpredictable ID (for example, 8+ or higher).
  • Reassign the content of deleted users to the author account.

Sessions and cookies

  • Set the Secure, HttpOnly, SameSite=Strict flags for cookies.
  • Limit the lifetime of administrator sessions.
  • Automatically end the sessions of inactive users (the Idle User Logout plugin).
  • Automatic logout of all of a user’s sessions when their role or password is changed by another admin — this prevents a situation where a downgraded or dismissed user keeps an active session.

APIs and interfaces

XML-RPC

In 2026 it is practically unnecessary (replaced by the REST API), but it remains a vector for DDoS and brute-force attacks. Disable it through a filter:

add_filter('xmlrpc_enabled', '__return_false');
It is better to block xmlrpc.php at the web server level (see the “Server environment” section).

WordPress Embeds (oEmbed)

If the content embedding functionality (embed) is not needed — disable it to reduce the attack surface:

// Remove oEmbed discovery links
remove_action('wp_head', 'wp_oembed_add_discovery_links');
// Remove oEmbed JavaScript
remove_action('wp_head', 'wp_oembed_add_host_js');
// Disable the oEmbed endpoint
add_filter('embed_oembed_discover', '__return_false');
// Deactivate embed rewrite rules
remove_action('rest_api_init', 'wp_oembed_register_route');

Comments

If the project does not need commenting on the frontend — disable comments completely at the core level. Open comments are a vector for spam, XSS and SQL injections through third-party commenting plugins.

  • In the admin panel: Settings → Discussion → uncheck “Allow people to submit comments on new posts”.

For a complete shutdown (including existing posts):

// Disable comment support for all content types
add_action('admin_init', function() {
$post_types = get_post_types();
foreach ($post_types as $post_type) {
remove_post_type_support($post_type, 'comments');
remove_post_type_support($post_type, 'trackbacks');
}
});
// Close comments on the frontend
add_filter('comments_open', '__return_false', 20, 2);
add_filter('pings_open', '__return_false', 20, 2);
// Hide existing comments
add_filter('comments_array', '__return_empty_array', 10, 2);
// Remove the "Comments" menu item from the admin panel
add_action('admin_menu', function() {
remove_menu_page('edit-comments.php');
});
Also remove the comments widget from the dashboard and remove the comments page from the admin bar.

Pingbacks and Trackbacks

Disable them completely — this is legacy functionality used as a vector for DDoS amplification and spam.

  • In the admin panel: Settings → Discussion → uncheck “Allow link notifications from other blogs (pingbacks and trackbacks) on new posts”.

Additionally through code:

add_filter('xmlrpc_methods', function($methods) {
unset($methods['pingback.ping']);
unset($methods['pingback.extensions.getPingbacks']);
return $methods;
});
add_filter('wp_headers', function($headers) {
unset($headers['X-Pingback']);
return $headers;
});

REST API

Restricting the REST API for anonymous users:

Can be disabled completely

  • An ordinary corporate site
  • A landing page
  • A blog without external integrations and headless architecture
Must not be disabled

  • Headless WP (React/Vue/Next.js)
  • WooCommerce with external integrations
  • Form or caching plugins that work through REST
  • Gutenberg blocks on the frontend that pull data through the API

The optimal approach — do not disable it completely, but selectively close dangerous endpoints (at least /wp/v2/users) and allow only the needed namespaces (for example, contact-form-7, oembed).

Restrict access to the endpoints that list users (/wp-json/wp/v2/users):

add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});

If the REST API is not used publicly — restrict access to authenticated users only.

User Enumeration

Block /?author=N enumeration:

if (!is_admin() && isset($_GET['author'])) {
wp_redirect(home_url(), 301);
exit;
}

Or through .htaccess / Nginx rewrite.

Hiding IDs (Posts, Users, Terms)

Hide the numeric IDs of users, posts and terms from public URLs and HTML markup — they make enumeration and targeted attacks easier.

  • Users: use a slug instead of an ID in author archives; remove the ID from the body class (author-1) through the body_class filter.
  • Posts: where possible, use pretty permalinks without numeric IDs.

Remove IDs from CSS classes (post-123, page-item-45):

add_filter('post_class', function($classes) {
return array_filter($classes, function($class) {
return !preg_match('/^(post|page-item)-\d+$/', $class);
});
});

Terms: similarly, remove IDs from menu and widget classes (menu-item-123, cat-item-5):

add_filter('nav_menu_css_class', function($classes) {
return array_filter($classes, function($class) {
return !preg_match('/^menu-item-\d+$/', $class);
});
});

Remove <meta name="generator"> from the head:

remove_action('wp_head', 'wp_generator');

File Uploads

  • Validate the MIME types of uploaded files (do not rely on the extension alone).
  • Block PHP execution in wp-content/uploads/ (see the “Server environment” section).
  • Scan uploaded files for malicious code.

Strictly limit the allowed file types through the upload_mimes filter — leave only the formats the project actually needs. By default, WordPress allows dozens of types (including SVG in newer versions), most of which are unnecessary:

add_filter('upload_mimes', function($mimes) {
return [
'jpg|jpeg|jpe' => 'image/jpeg',
'png'          => 'image/png',
'gif'          => 'image/gif',
'webp'         => 'image/webp',
'pdf'          => 'application/pdf',
];
});
Block the upload of potentially dangerous formats: .svg (an XSS vector without sanitization), .exe, .php, .sh, .sql.

Content-Disposition header — configure the server to serve files from uploads with the Content-Disposition: attachment header, so the browser does not execute them but offers them for download:

Nginx:

location ~* /wp-content/uploads/ {
add_header Content-Disposition "attachment";
}

WAF and network protection

External WAF (CDN)

  • Cloudflare (or Sucuri, Cloud Armor) — recommended for all projects.
  • A CDN hides the server’s real IP address and protects against DDoS.

Configure WAF Rules:

  • Block access to xmlrpc.php.
  • Challenge (Captcha) for /wp-admin for all countries except yours.
  • Block requests with suspicious User-Agents.
  • Rate limiting on critical endpoints.

Internal WAF (plugins)

  • Wordfence, Solid Security or equivalents — for protection at the WordPress level.
  • Monitoring and blocking of suspicious requests.

Network firewall

  • Configure iptables/ufw: close all ports except the necessary ones (SSH, HTTPS).
  • Fail2Ban or OSSEC for automatic blocking of suspicious IPs.

Monitoring and logging

Activity Log

  • Install WP Activity Log or an equivalent.
  • Record: logins, plugin installations/updates, settings changes, content edits.
An Activity Log is the “digital surveillance camera” of the admin panel. In the event of an incident, it lets you establish the cause.

Login Notification

Set up an email notification to the admin on every login to the admin panel — this lets you promptly detect unauthorized access, even if the attacker has bypassed the other layers of protection.

File Integrity Monitoring

  • Track changes in the hashes of core, plugin and theme files (Wordfence, OSSEC).
  • wp core verify-checksums — regularly through cron.

Server logs

  • Regularly analyze access and error logs for suspicious activity.
  • Set up alerts for an anomalous number of 404s, 403s or attempts to access the admin panel.

Backups

  • At least 3 copies in different storage locations (server + cloud: S3, Google Drive, Backblaze).
  • Never store backups only on the same server.
  • Frequency: daily for active sites, weekly for static ones.
  • Incremental + full backups.
  • Automate them through plugins (UpdraftPlus, BlogVault) or server scripts.
  • Store backups encrypted.
Be sure to test restoration from a backup (a “fire drill”).
Encrypt DB dumps separately — they contain passwords, email addresses, users’ personal data and tokens. An unencrypted DB dump in the wrong hands = complete compromise of the site.

DNS and Email Security

DNS

  • Enable DNSSEC to protect against DNS spoofing.
  • Hide the server’s real IP behind a CDN (Cloudflare, etc.).

Email (protecting the domain from phishing)

Record Purpose
SPF Specify the servers allowed to send mail
DKIM Digital signature for emails
DMARC Policy for handling emails that fail verification

Disclosure and documentation

security.txt

Add a /.well-known/security.txt file (RFC 9116) for responsible disclosure:

Contact: mailto:security@example.com
Preferred-Languages: uk, en
Expires: 2027-01-01T00:00:00.000Z

Documentation

  • Document all security settings (without passwords in plain text).
  • Develop a disaster recovery plan (DRP).
  • Keep the documentation up to date.

Administrator workstation security

  • An up-to-date OS, browser, antivirus/EDR.
  • Do not use the admin account for ordinary browsing.
  • Avoid public Wi-Fi when working with the site; use a VPN.
  • Train the team: phishing, social engineering, the mandatory nature of 2FA.
  • Assign someone responsible for security monitoring.
Remember: a compromised admin computer = a compromised site.

Action Plan (quick start)

The minimal set of actions for basic protection of a new WordPress project:

  1. Check the PHP version (8.3+)
  2. Add DISALLOW_FILE_EDIT and DISABLE_WP_CRON to wp-config.php
  3. Update the Salts
  4. Set up 2FA for all admins
  5. Block xmlrpc.php at the server level
  6. Disable pingbacks/trackbacks
  7. Disable comments (if the project does not need them)
  8. Limit the allowed file upload formats (upload_mimes)
  9. Block PHP execution in uploads/ and wp-includes/
  10. Remove readme.html, license.txt, wp-config-sample.php
  11. Block user enumeration (author, REST API users)
  12. Hide the IDs of users, posts and terms from public markup
  13. Delete/downgrade admin accounts with IDs 1–10, create an admin with a higher ID
  14. Configure Security Headers
  15. Set up backups to external storage
  16. Install an Activity Log plugin
  17. Check file permissions (644/755/600)
  18. Configure Cloudflare WAF
  19. Set up rate limiting on wp-login.php at the server level
  20. Filter HTTP methods (only GET/POST/HEAD)
  21. Set up login notifications for admins
  22. Set up protection against hot-linking
  23. Configure SPF/DKIM/DMARC
  24. Add security.txt
  25. Disable public registration (if not needed)
  26. Disable oEmbed (if not needed)
  27. Add an X-Robots-Tag noindex for wp-admin
  28. Test restoration from a backup
  29. Encrypt DB dumps separately

Vitalii Kaplia

Founder, Web Developer & WordPress Expert

I became interested in programming back in 1997. The first acquaintance with a future profession was using Visual Basic. In…

More about author

A digital product engineer and web solutions architect

Free consultation + cost calculation

Let’s discuss your project?

Free consultation + cost calculation

More interesting articles

Customer login

This site uses cookies

We use cookies to personalize content and ads, provide social media features, and analyze our traffic. We also share information about your use of our website with our social media, advertising, and analytics partners, who may combine it with other information you have provided to them or collected when you use their services. By continuing to use our site, you consent to our use of cookies and accept our Privacy Policy and Terms of Use.

Virtual assistant
Hi! I'm the KAPLIA.PRO virtual assistant! I can tell you about our services, portfolio projects, ballpark pricing, and how we work — from websites and eCommerce to web apps, CRMs, bots, and AI agents. Ask away!

By continuing with the AI assistant, I agree to the site’s terms of use and privacy policy.

AI assistant may make mistakes in responses