My regular security checklist. It compiles a set of manual actions to strengthen the security of a WordPress website. I use it myself for the sites I manage daily. It is not exhaustive and may evolve over time, but it provides a solid foundation to build on.
This security checklist is based on my personal approach of providing lightweight, granular solutions. You won’t find any large “all-in-one” security plugins here. While they may work, they are often heavier and more resource-intensive. My goal is to give you a list of immediately applicable actions to strengthen your site’s security, assuming it has not already been compromised. If it has, you will need at least one tool that includes an antivirus scan.
Now that this is clear, let’s move on to strengthening your site!
The base of the base
- The human factor! Always follow the basic rules of digital security, especially the first one: use a strong, unique password for each access. And never share your passwords by email.
- Choose a quality, secure hosting provider. If it offers services tailored specifically for WordPress, even better. Some of the best practices listed here may already be implemented by default.
- Update WordPress regularly. If you do not have a maintenance provider, at the very least enable automatic updates for minor versions.
- Update plugins and themes regularly to ensure they remain secure.
- Make sure you back up your site daily. Many web hosts offer automatic backups, so be sure to activate them.
- Limit the number of administrator users. With great power comes great responsibility. Site administrators must be trained to use the admin interface and informed of any changes. If someone only needs to manage content, use the Editor role instead.
- Avoid using pirated themes/plugins (say no to “nulled” or “GPL free” versions). From a security standpoint, unless you can personally audit that $5 nulled wp-rocket plugin, installing plugins modified by an untrusted third party is a very bad idea. This is a common vector for site hacking.
. htaccess – Limit unwanted access and secure critical files.
Disable file listing
By disabling directory listing, you prevent visitors from seeing the complete list of files stored in your folders.
For example, a directory like /wp-content/uploads/
contains uploaded files. Without this rule, an attacker could access https://your-site.com/wp-content/uploads/
and view all the files. By adding the directive Options -Indexes
in your .htaccess
file, this request will instead return a 403 (Access Denied) error instead of displaying the folder contents.
# Directories read protection
Options -Indexes
Code language: Apache (apache)
Block access to critical files
Protect the sensitive files that contain the technical information and configuration of your WordPress site.
Details:
.htaccess
: Controls important server behavior. If modified or accessed, an attacker could disable protections or insert malicious directives.wp-config.php
: Contains critical information such as database connection credentials.readme.txt
: These files can reveal the exact version of your WordPress installation or the plugins you use, which makes targeted attacks easier.
# Protect .htaccess file
<files .htaccess>
order allow,deny
deny from all
</files>
# Protect wp-config.php file
<files wp-config.php>
order allow,deny
deny from all
</files>
# Redirect Readme file(s) access to 404
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule (/|^)(readme|changelog|license)\.(txt|html|md)$ - [R=404,L,NC]
</IfModule>
Code language: Apache (apache)
Block access to development files
These files may contain sensitive information such as PHP errors, configuration details, or user data.
# Blocks access to error_log files containing PHP errors
<Files ~ "error_log">
Deny from all
</Files>
# Blocks access to database files
<Files ~ "\.(sql\.gz|sql\.zip|sql)$">
Deny from all
</Files>
# Block direct access to development files
<FilesMatch "^\.(env|git|log|bak)$">
Order Allow,Deny
Deny from all
</FilesMatch>
Code language: AngelScript (angelscript)
Hide WordPress version
Attackers often use tools to detect the exact version of WordPress installed on a site. If a particular version is known to have security vulnerabilities, this makes targeted attacks easier. Hiding this information reduces your site’s exposure to such attempts.
By default, WordPress may include version information in HTTP headers, such as X-Powered-By
, or other visible metadata. This rule removes that information.
# Hide WordPress version in HTTP head
<IfModule mod_headers.c>
Header unset X-Powered-By
</IfModule>
Code language: Apache (apache)
Block access to xmlrpc.php
xmlrpc.php
allows external applications to communicate with your site, such as publishing software outside the WordPress editor. This feature is enabled by default but is rarely used on modern sites. It can serve as a gateway for brute-force or DDoS attacks. This rule blocks access to the file managing this feature. A short code snippet is also provided to disable this feature directly in WordPress.
# Blocks access to the xmlrpc file
<Files xmlrpc.php>
order deny,allow
deny from all
</Files>
Code language: Apache (apache)
Forcing an HTTPS redirect
Ensures that all connections to the site use a secure protocol, protecting data exchanged between the server and users from interception. This prevents visitors from accessing the site via an unsecured connection (HTTP), which could expose their sensitive information to man-in-the-middle attacks.
A typical scenario is an e-commerce site where sensitive data, such as login credentials or payment details, are transmitted. Enforcing HTTPS redirection strengthens the confidentiality and security of these transactions.
# Force HTTPS
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} !on
RewriteRule ^(.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
Code language: Apache (apache)
SEO Bonus
Forcing trailing slashes on URLs without plugins
Allows you to standardize your site’s URL structure. This prevents search engines from treating URLs with and without a trailing slash as separate pages with identical content, which can dilute SEO value.
For example, https://example.com/page
will be redirected to https://example.com/page/
, ensuring consistent access to a single page.
# Force trailing slashes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_METHOD} GET
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1/ [L,R=301]
</IfModule>
Code language: Apache (apache)
Force redirection of WWW subdomain
Forcing redirection of the WWW subdomain helps standardize site access by redirecting all requests either to the WWW version or the non-WWW version.
Choose based on your preference: with or without WWW. Ensure you enter your actual domain name, not a placeholder like name.domain.
# Redirect www to non-www
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.name\.domain [NC]
RewriteRule ^(.*)$ https://name.domain/$1 [L,R=301]
</IfModule>
Code language: Apache (apache)
# Redirect non-www to www
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^name.domain
RewriteRule (.*) https://www.name.domain/$1 [L,R=301]
</IfModule>
Code language: Apache (apache)
wp-config.php – Reinforce WordPress configuration.
Prevent errors from being displayed
Prevents the exposure of sensitive information about your server configuration or site structure.
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', false);
Code language: PHP (php)
Disable file editor
Enhances security by preventing users (or attackers) from modifying theme and plugin files through the WordPress admin interface.
For example, if an attacker gains control of an administrator account, they could access the file editor to inject malicious code or alter critical files.
define('DISALLOW_UNFILTERED_HTML', true); //CAN BREAK SVG ON FRONTEND
define('DISALLOW_FILE_EDIT', true);
Code language: PHP (php)
Force repair page deactivation
Forces the deactivation of the repair page to prevent unauthorized access to WordPress’ built-in database repair tool. If publicly accessible, this page could reveal sensitive information, including details of the database structure.
define('WP_ALLOW_REPAIR', false);
Code language: PHP (php)
Filter uploads
Filter uploads to control the types of files that can be uploaded to your site, reducing the risk of malicious files being executed.
Without this filtering, an attacker could upload a PHP file disguised as an image and execute it on the server to gain unauthorized access.
define('ALLOW_UNFILTERED_UPLOADS', false);
Code language: PHP (php)
Prevent site domain name changes
Preventing site domain name changes locks the URL settings and prevents unauthorized modification of the site address in WordPress administration. This can be crucial in preventing an attacker from modifying your site’s URLs to redirect visitors to a malicious site.
define('WP_ENVIRONMENT_TYPE', 'production');
define('WP_HOME', 'https://name.domain/');
define('WP_SITEURL', 'https://name.domain/');
Code language: PHP (php)
Force secure connection to administration interface
A secure connection to the administration interface is enforced, ensuring all exchanges between the user and the site occur exclusively over HTTPS. This safeguards sensitive information—such as login credentials and administrative actions—from potential interception during transmission.
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);
Code language: PHP (php)
Force WordPress minor auto-updates
Ensures your WordPress site automatically applies security patches and minor updates without manual intervention. This reduces risks tied to vulnerabilities in older versions, shrinking the window of opportunity for attackers. I also take this opportunity to disable the automatic download of new default WordPress themes, which I don’t need on a production site.
define('WP_AUTO_UPDATE_CORE', 'minor');
define('CORE_UPGRADE_SKIP_NEW_BUNDLED', true);
Code language: PHP (php)
Configuring security keys
Configuring your site’s security keys enhances user session protection by encrypting login and session cookies. These unique, secret keys ensure that stolen cookies are unusable, as they cannot be authenticated without the correct keys.
Replace the relevant section in your file with the keys generated here: WordPress Security Key Generator.
define('AUTH_KEY', ']AP.n.;g2t}^H^W ~&-19},0g+]*XnJsG2{dd!.2l|`$s>$<8%7J$.UK s7L>+Cj');
define('SECURE_AUTH_KEY', 'RLNn^ &ns#Ady!Xlg!N|AstpB?_Yen T}sc;$&wS+^T&%+~E *c]1pie.eTmyZHZ');
define('LOGGED_IN_KEY', 'H]BJj1~XK?z<+##=Y/+wuUrT:!*.YWleLxZ>_0XO@J3>3(eyJY8rR,4V@h==I|Y;');
define('NONCE_KEY', 'iLCnH=E$pdU4il%*7Fjq^Y3N7fD^o|5;,C*-.1dyp-?A;*cA>;8J0EipnDNH4sHb');
define('AUTH_SALT', '&-.WI#@UJj4BQi@T={^m][L</8BWIHn)ZeF#5 .#vnO%cC_6dEcn,JgMZXXv/m6t');
define('SECURE_AUTH_SALT', '%A$*vRs1BKlZ@Lb<1R;||u|UC`%9a]-cKis#AL<cS~.^[pkZZ/G[zOY&6KDzL)2S');
define('LOGGED_IN_SALT', '-XLU6pmXcH(73_cGw-msP2)PhuYO>>$SR`3IY1--3Z,|OtHM1Z%|XC%?#(TAKC}/');
define('NONCE_SALT', 'b-OIt6B8^xy!{N/Q`PxmzwdIfK)/oiq%gM,^r(bAl|`nr/)>^M0qr{[i ?ZR8$xN');
Code language: PHP (php)
functions.php – Modify or disable default WordPress behaviors.
Hide connection errors
Hide login errors to prevent attackers from determining whether the username or password is incorrect. Displaying a generic message like “login error” reduces the information available for brute-force attacks.
// Remove login error message
add_filter('login_errors', function() {
return 'Erreur de connexion.';
});
Code language: PHP (php)
Disable XML-RPC
In the .htaccess
file, I added a rule to block access to xmlrpc.php
. This snippet disables the associated WordPress functionality.
// Disable XMLRPC
add_filter('xmlrpc_enabled', '__return_false');
remove_action('wp_head', 'rsd_link');
Code language: PHP (php)
Disable pingbacks and trackbacks
These features can be exploited to send spam or, in more severe cases, to launch DDoS attacks by using the victim site as a relay to flood another site with requests. Blocking access to these notifications prevents attackers from weaponizing the site to send large volumes of requests.
// Remove X-Pingback headers
add_filter('wp_headers', 'zenpress_remove_x_pingback');
function zenpress_remove_x_pingback($headers) {
unset($headers['X-Pingback']);
return $headers;
}
// Disable pingbacks and trackbacks for new articles
add_action('after_setup_theme', 'zenpress_disable_pingback_and_trackback');
function zenpress_disable_pingback_and_trackback() {
update_option('default_ping_status', 'closed');
update_option('default_pingback_flag', 0);
}
// Self Pingbacks
add_action('pre_ping', function (&$links) {
$home = get_option('home');
foreach ($links as $l => $link) {
if (strpos($link, $home) === 0) {
unset($links[$l]);
}
}
});
Code language: PHP (php)
Hide WordPress and WooCommerce version
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', 'zenpress_remove_wordpress_version');
function zenpress_remove_wordpress_version() {
return '';
}
// Remove WordPress version from scripts and styles
add_filter('style_loader_src', 'zenpress_remove_version_from_style_js');
add_filter('script_loader_src', 'zenpress_remove_version_from_style_js');
function zenpress_remove_version_from_style_js($src) {
if (strpos($src, 'ver=' . get_bloginfo('version'))) {
$src = remove_query_arg('ver', $src);
}
return $src;
}
Code language: PHP (php)
if (class_exists('woocommerce') && !is_admin()) {
// Hide WooCommerce version from HTTP headers
add_filter('wp_headers', 'zenpress_remove_woocommerce_version');
function zenpress_remove_woocommerce_version($headers) {
if (isset($headers['X-WooCommerce-Version'])) {
unset($headers['X-WooCommerce-Version']);
}
return $headers;
}
// Remove WooCommerce version from scripts and styles URLs
add_filter('style_loader_src', 'zenpress_remove_woocommerce_version_scripts_styles', 10);
add_filter('script_loader_src', 'zenpress_remove_woocommerce_version_scripts_styles', 10);
function zenpress_remove_woocommerce_version_scripts_styles($src) {
if (strpos($src, 'ver=') && strpos($src, 'woocommerce')) {
$src = remove_query_arg('ver', $src);
}
return $src;
}
}
Code language: PHP (php)
Limit connection attempts
Reduces the risk of brute-force attacks, where an attacker tries to guess passwords by testing countless combinations.
add_filter('login_errors', function () {
return __('Login error.', 'your-theme');
});
add_filter('authenticate', 'zenpress_login_protection', 30, 3);
function zenpress_login_protection(mixed $user, string $username, string $password): mixed {
$MAX_LOGIN_ATTEMPTS = 5;
$BLOCK_DURATION = 30; // 5 minutes
$ipAddress = isset($_SERVER['REMOTE_ADDR']) ?
filter_var(wp_unslash($_SERVER['REMOTE_ADDR']), FILTER_VALIDATE_IP) : 'unknown';
$transientKey = 'zenpress_login_block_' . $ipAddress;
if ($user instanceof WP_User) {
delete_transient($transientKey);
return $user;
}
if (get_transient($transientKey)) {
header('HTTP/1.0 403 Forbidden');
die('Access Denied');
}
$attemptsData = get_transient('zenpress_login_attempts_' . $ipAddress);
$attempts = $attemptsData['count'] ?? 0;
$attempts++;
if ($attempts > $MAX_LOGIN_ATTEMPTS) {
set_transient($transientKey, true, $BLOCK_DURATION);
header('HTTP/1.0 403 Forbidden');
die('Access Denied');
}
set_transient('zenpress_login_attempts_' . $ipAddress, ['count' => $attempts], $BLOCK_DURATION);
return $user;
}
Code language: PHP (php)
Additional plugins – Advanced protection and Firewall
Firewall
BBQ Firewall is designed to protect WordPress sites from common attacks, such as SQL injections, XSS (cross-site scripting), and other malicious attempts. This plugin uses a set of lightweight rules to strengthen your site’s security.
➡️ BBQ Firewall – Fast & Powerful Firewall Security by Jeff Starr
Note: This firewall may impact custom developments, but you can customize its basic configuration to fit your needs and disable protections that could cause issues: Customize BBQ Firewall.
Move Login Page (/wp-login.php)
Makes it harder for attackers to access the administration interface. By changing the default login page URL (usually /wp-login.php), you obscure this entry point, making it more difficult for attackers to automate login attempts or brute-force attacks.
➡️ WPS Hide Login by WPServeur, NicolasKulka, wpformation
Strengthening HTTP Headers
The Headers Security Advanced & HSTS WP plugin lets you easily configure security headers like X-Content-Type-Options, X-XSS-Protection, Content-Security-Policy, and HTTP Strict Transport Security (HSTS), adding layers of protection against common attacks.
➡️ Headers Security Advanced & HSTS WP by Andrea Ferro
Leave a Reply