I'm in the process of adding yet another layer of security to some of my WordPress installs, since some of my customers sites use shared hosting, and generally they do not ban users for brute force attempts on the WordPress login pages. Generally I would use fail2ban but sadly these accounts are on shared hosting and not in the position to make use of it.

I know there are WordPress plug-ins out there to prevent brute force but would rather a simple .htaccess method that will pretty much block all attempts via the wp-login.php file.

So I came up with this simple code that will block users other than myself, or at least those not using my ISP.

<FilesMatch "wp-login.php">
    deny from all
    allow from .isp.example.com

The code works great however I would expect to see a 403 forbidden yet Firebug reveals 302 temporarily moved, the header status return looks like this:

HTTP/1.1 302 Moved Temporarily    
Date: Thu, 31 Dec 2015 00:24:15 GMT    
Server: Apache    
X-Powered-By: PHP/5.3.29    
Expires: Wed, 11 Jan 1984 05:00:00 GMT    
Cache-Control: no-cache, must-revalidate, max-age=0    
Pragma: no-cache    
Link: <http://www.example.com/wp-json/>; rel="https://api.w.org/"    
Location: http://www.example.com/wp-login.php    
Vary: Accept-Encoding    
Content-Type: text/html; charset=UTF-8    
Transfer-Encoding: chunked

While this does seem to stop the brute attacks it causes unnecessary multiple access attempts on the server, for example here is what FireBug looks like:


Other than using a redirect rather than deny is there anything I can do about this??

  • How can I return 403 with deny (not redirect)
  • Is this normal? or should I contact the web host?
  • Good one! Your code looks good to me. Why it gives you a 302 is a mystery. I will be thinking about this one for a while... the only thing that comes to mind immediately is not using files match thing and just doing the standard RewriteCond/RewriteRule combo since that should work.
    – closetnoc
    Dec 31, 2015 at 2:21
  • I'd like to see you post the answer to this @Simon
    – Steve
    Jan 1, 2016 at 2:02
  • Is the 403 document registered? ErrorDocument 403 /403.php
    – Evgeniy
    Jan 8, 2016 at 23:24

3 Answers 3


Try this if you have rewrite module installed:

RewriteEngine On
RewriteCond %{REQUEST_URI} ^(.*)wp-login.php$
RewriteCond %{REMOTE_ADDR} !^xxx\.xxx\.xxx\.xxx$
RewriteRule ^(.*)$ - [R=403,L]

replace the xxx's with each octet of your IP address. For example, if your IP address is 111.222.333.444, then replace

RewriteCond %{REMOTE_ADDR} !^xxx\.xxx\.xxx\.xxx$


RewriteCond %{REMOTE_ADDR} !^111\.222\.333\.444$

If that won't work, then use this:

RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^xxx\.xxx\.xxx\.xxx$
RewriteRule ^(.*)wp-login.php$ - [R=403,L]

Nice thing with these scripts is that the rules also apply to any kind of URL that ends in wp-login.php which I highly doubt 99% of websites will want to make available to the general public.

I also suggest using IP addresses instead of hostnames because the hostname will have to eventually resolve to an ip address anyway, and because updating .htaccess takes no time for apache to recognize the changes, I would suggest that each time you're logged into your ISP, you note your IP address and use that one in the script I shown above so that only your computer has access to the wp-login.php script.


Just to add something to this for others who may be experiencing the same issue. I was receiving the same series of 5 sequential 302 errors whether I was using:

<Files wp-login.php>
order deny,allow
deny from all
allow from xxx.xxx.xxx.xxx

or using the rewrite rule shown above in my .htaccess file.

The reason for this was because I had failed to create a '403.shtml' page I created the page using cpanel and now receive a single '403' error when an denied IP address attempts to access the file... Like an idiot I had assumed that apache would generate a default 403 page which it does correctly on non-Wordpress sites - but it seems that wordpress tries to handle the error page itself - creating a 403.shtml forces wordpress to let .htaccess do its job...


Is this normal? or should I contact the web host?

No, this is not normal.

X-Powered-By: PHP/5.3.29
Link: <http://www.example.com/wp-json/>; rel="https://api.w.org/"  

But the HTTP response header shown has passed through PHP (WordPress?), not just Apache - as you would expect with a raw deny from ... directive. That Link: header suggests WP is somehow responsible.

Either there is some kind of conflict? Or a custom (ie. PHP) 403 ErrorDocument could be defined and overriding the expected response? Or the request is being routed through WP / what URL is being requested?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.