Most attacks on WordPress sites are not aimed at you personally. They come from bots that roam a huge number of sites and try the same doors everywhere. Those doors are the login page, the xmlrpc.php file, old plugins and files that should never open in a browser. Securing a WordPress site starts with keeping those doors shut.
This guide goes through three places you can set up yourself, without touching the site's code. They are the wp-config.php file, the .htaccess file and Cloudflare's free plan. Every step comes with ready code and an explanation of what it does, and at the end there is a checklist to make sure nothing was missed.
Where attacks get into a WordPress site
The routes are the same almost everywhere. The most common one is a plugin or theme on an old version with a known hole. As soon as the fix is released, the description of the hole becomes public and bots start looking for sites that have not updated yet.
The second route is the login. Bots try username and password combinations on wp-login.php and through xmlrpc.php, where a single request can try many passwords at once. The third is a file left reachable, such as a copy of wp-config.php or a backup in the site's folder. The fourth is a PHP file uploaded through a vulnerable plugin into the uploads folder and run from there.
Updates remain the most important part. The settings below lower the chance that a bot finds an open door before you update, and they close the three routes that updates do not cover.
What to add to wp-config.php
The wp-config.php file is in the site's main folder, usually public_html. Open it in File Manager with Edit and add the lines above the comment that tells you to stop editing (That's all, stop editing!).
// wp-config.php, above "That's all, stop editing!"
// The theme and plugin editor in the admin is turned off.
define( 'DISALLOW_FILE_EDIT', true );
// The login and the admin work only over HTTPS.
define( 'FORCE_SSL_ADMIN', true );
// Errors are not shown to visitors.
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_DISPLAY', false );
The first line removes the file editor from the admin. If someone logs in with a stolen password, they cannot write malicious code into the theme straight from the browser. The second makes the login and the admin go over HTTPS, so the password never travels in the clear. It needs an SSL certificate on the site, as described in the HTTPS guide. The last two lines keep error messages away from visitors, because they give away paths and versions.
Change the keys if you suspect a leak
wp-config.php also holds eight keys (AUTH_KEY, SECURE_AUTH_KEY and others) that WordPress uses to sign the login cookies. If you suspect that a password or the file itself has leaked, change them. Every logged-in user is logged out and has to log in again.
# cPanel Terminal, in the site's folder
wp config shuffle-salts
Without a terminal, get new keys from api.wordpress.org/secret-key/1.1/salt and replace the eight lines in wp-config.php with them.
What to close in .htaccess
The .htaccess file is in the same folder as wp-config.php. It is hidden, so in File Manager turn on Show Hidden Files under Settings. Add the lines above the # BEGIN WordPress block, never inside it, because WordPress rewrites that block when you change the permalinks.
# .htaccess in the site's folder, above # BEGIN WordPress
# No file listing in folders without an index file.
Options -Indexes
# wp-config.php does not open in a browser.
<Files wp-config.php>
Require all denied
</Files>
# xmlrpc.php is closed (see the note below).
<Files xmlrpc.php>
Require all denied
</Files>
The wp-content/uploads folder should hold only images and documents. If a vulnerable plugin lets someone upload a PHP file, that is usually where it ends up. Create a new .htaccess file in uploads with this content.
# wp-content/uploads/.htaccess (new file)
# PHP files in uploads do not run.
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
Check that it works. Open the xmlrpc.php address in the browser or run the command from your computer. The answer should be 403.
# From your computer. You expect a 403 answer.
curl -I https://primer.bg/xmlrpc.php
The free Cloudflare rules for wp-login.php and wp-admin
If the site is behind Cloudflare, bots can be stopped before they ever reach the server. The free plan already includes DDoS protection and Cloudflare's managed rules against known vulnerabilities.
On top of those you can add your own rules. The free plan allows only a few, so we use the three that give the most. Open Security, then WAF and Custom rules, click Create rule and choose Edit expression.
# Rule 1, action Block
(http.request.uri.path eq "/xmlrpc.php")
# Rule 2, action Managed Challenge
(http.request.uri.path eq "/wp-login.php" and ip.src.country ne "BG")
# Rule 3, action Managed Challenge
(http.request.uri.path contains "/wp-admin/"
and http.request.uri.path ne "/wp-admin/admin-ajax.php"
and ip.src.country ne "BG")
The first rule stops xmlrpc.php completely. The second and third show a challenge only to visits from outside Bulgaria. You will not see anything, while a bot from the other side of the world has to pass the challenge before it reaches the login form. admin-ajax.php is left out because some themes and plugins use it on the public side of the site as well.
Updates and WP Toolkit
Everything so far lowers the chance that a bot finds an open door. Updates close the doors themselves. WordPress applies minor core versions on its own, but the theme and the plugins wait for you.
If your cPanel has WP Toolkit, it gathers all your WordPress sites in one place and updates them from there. It also checks the security of each site and notifies you when it finds a risk. Delete the plugins you do not use as well. A deactivated plugin is still code on the server and can still have a hole.
Checklist
- There is a backup of the site from before the changes.
- wp-config.php has
DISALLOW_FILE_EDITandFORCE_SSL_ADMINset to true, andWP_DEBUG_DISPLAYset to false. - The .htaccess in the site's folder closes directory listings, wp-config.php and xmlrpc.php.
- The .htaccess in uploads stops PHP files.
- The xmlrpc.php address returns 403.
- Cloudflare has rules for xmlrpc.php, wp-login.php and wp-admin.
- WordPress, the theme and the plugins are on their latest versions, and unused plugins are deleted.
- Administrator passwords are long and not used anywhere else.
If you would rather not do it yourself
Not every site needs all of this at once. If the site is small and rarely changes, start with a backup, updates and the Cloudflare rules. They give the most for the least time.
If you want to know what is still open, with the security and speed audit we check the site and the server and give you a report of what to fix, in order of importance. If the site is on your own server, with WordPress maintenance we set up the firewall, SSH and permissions and monitor the site and the server with alerts that reach an engineer.
Frequently asked questions
How do attackers most often get into a WordPress site?
The most common route is a plugin or theme on an old version with a known hole, because as soon as the fix comes out the hole's description is public and bots look for sites that have not updated. Next come logins through wp-login.php and xmlrpc.php, a file left reachable such as a copy of wp-config.php or a backup in the site's folder, and a PHP file uploaded through a vulnerable plugin into the uploads folder.
What matters most for WordPress security?
Updating WordPress, the theme and the plugins. Settings in wp-config.php, .htaccess and Cloudflare lower the chance that a bot finds an open door before you update, but the updates are what shut the doors. Delete plugins you do not use as well, since a deactivated plugin is still code on the server and can still have a hole.
What should I add to wp-config.php for better security?
DISALLOW_FILE_EDIT set to true removes the file editor from the admin, so someone with a stolen password cannot write code into the theme from the browser. FORCE_SSL_ADMIN set to true sends the login and the admin over HTTPS, and WP_DEBUG_DISPLAY set to false hides error messages that give away paths and versions. Add the lines above the That's all, stop editing! comment after making a copy of the file.
Should I block xmlrpc.php?
If you do not use it, yes, because bots try passwords through xmlrpc.php, where a single request can try many at once. Jetpack and some publishing apps do use xmlrpc.php, though. If you rely on them, do not close it in .htaccess and leave it to a Cloudflare rule instead, which you can switch off with one button.
How do I stop PHP from running in the uploads folder?
Create a new .htaccess file in wp-content/uploads with a rule that stops PHP files. The folder should hold only images and documents, and a PHP file uploaded through a vulnerable plugin usually ends up exactly there. With the rule in place, such a file cannot run even if it gets uploaded.
When should I change the keys in wp-config.php?
When you suspect that a password or the wp-config.php file itself has leaked. The eight keys, such as AUTH_KEY and SECURE_AUTH_KEY, sign the login cookies, and new ones are available from api.wordpress.org/secret-key/1.1/salt. The change logs out every logged-in user, you and the editors included, so do it when nobody is working on the site.
Which free Cloudflare rules should I add for WordPress?
The free plan allows only a few custom rules, so add the three that give the most under Security, WAF and Custom rules. The first blocks xmlrpc.php completely, and the other two show a challenge for wp-login.php and wp-admin only to visits from outside Bulgaria. admin-ajax.php is left out, because some themes and plugins use it on the public side of the site.
What if I also work on the site from abroad?
Add the country to the Cloudflare rules, for example not ip.src.country in {"BG" "GR"} instead of ip.src.country ne "BG". Otherwise you will have to pass the challenge every time you log in to the admin from outside Bulgaria.