Rate limiting access to wp-login.php

I had some kind of attack on my server but from millions of random IPs so I couldn’t effectively block them.

I then had the idea to rate limit access to wp-login.php to say 2 accesses per minute as most sites have very few users so this should be the easiest way but I’m not sure if this is a good solution?

If you think its good, here is what I tried but failed with error:

19.10.2015-15:31 - WARNING - Reason for nginx restart failure: Restarting nginx: nginx failed! nginx: [emerg] unknown limit_req_zone “one” in /etc/nginx/sites-enabled/100-israelsafety.com.vhost:192 nginx: configuration file /etc/nginx/nginx.conf test failed

inside nginx.conf:

    limit_req_zone  $binary_remote_addr  zone=one:10m   rate=1r/m;

its very low at 1r/1m for testing purposes

inside vhost.conf: location ~ /wp-login(/.php) { try_files $uri =404; include /etc/nginx/fastcgi_params; fastcgi_pass unix:/var/lib/php5-fpm/web1.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors on; fastcgi_buffer_size 128k; fastcgi_buffers 256 16k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_read_timeout 3000; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache WORDPRESS; fastcgi_cache_valid 60m; limit_req zone=one burst=5; }

Anyone see any errors here? Is this line correct: limit_req zone=one burst=5; ?

1 Like

EasyEngine is already rate limiting things, you could look at the config used in EE as an example.

We’ve taken that one step further and white listed /wp-admin/ and wp-login.php to only certain IPs. Even if your ip changes you can update your white list config include and restart nginx

Any idea how to see that example without installing EE?

Sure, no problem.

Include something like this in your nginx confs:

# Limit access to avoid brute force attack
location = /wp-login.php {
        ## 04/08/2015 - Mod to restrict wp admin
        include /etc/nginx/common/wp-restrict-admin-ip.conf;
        ## End restrict mod
        limit_req zone=one burst=1 nodelay;
        include fastcgi_params;
        fastcgi_pass php;
## 04/08/2015 - Mod to restrict wp admin
## New blocks
location ~ ^wp-admin {
  include /etc/nginx/common/wp-restrict-admin-ip.conf;

location ~* ^/wp-admin/admin-ajax.php$ {
  allow all;
location ~* ^/wp-admin/.*\.php {
  include /etc/nginx/common/wp-restrict-admin-ip.conf;

## End New Blocks

EE uses a common include, wpcommon.conf which is in /etc/nginx/common – you’ll need to adjust as necessary for your non-ee

Then in wp-restric-admin-ip.conf we have a list of IPs that are white listed with a final deny all

allow xxx.xxx.xxx.xxx;
allow xxx.xxx.xxx.xxx;
deny all;

Something like this should work for you even if not on EE

Thanks a lot, will give it a try!

Why don’t you use fail2ban ?

This is my settings for EasyEngine.


I already use fail2ban. let me explain my thinking, maybe I am completely wrong though…

I use wpfail2ban which logs every failed login attempt. fail2ban parses and blocks IPs with say 5 failed logins / 24h.

I now faced a very slow brute force attack meaning every IP only tried to login once a day but I was hit with thousands of IPs.

So what I was thinking about was rate limit access to the WP login for everyone regardless of who they are but I am coming to realize that this is not possible… (I was thinking kinda - only allow 5 accesses to login per minute… but then if I am constantly hit, I would have to somehow whitelist myself which is not possible as I don’t have a fixed IP).

Just thinking out loudly here: so what if I simply drop every access to wp-login.php and wp-admin that doesn’t show my own sites as referer?

Why don’t you run a SSH Tunnel, and configure your browser to access your site through your own server?

In such case all you have to do is whitelist the server IP and lock everybody else out.

If you need assistance setting this up let me now. It is a breeze under Linux or Mac OS. Unfortunately I’mont a Windows user for more than ten years now, and I wouldn’t be able to help under this environment.

This is a multiuser environment. I know how to tunnel but that’s not a viable option for the other users. THanks for the idea though :slight_smile:

P.S. On Windows you can use putty or kitty to setup a ssh tunnel. Works very well.

Downgrade rate limit access to the WP login to 30r/m is a good choice. You need enable 403block jail in my fail2ban settings, because if they ddos wp-login, they will meet 403 error, and fail2ban will ban them after 3 times.

This is my log 0.044 BYPASS [07/Nov/2015:16:32:41 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.024 BYPASS [07/Nov/2015:16:32:42 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.024 BYPASS [07/Nov/2015:16:32:42 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3483 "-" "-" 0.026 BYPASS [07/Nov/2015:16:32:43 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" - BYPASS [07/Nov/2015:16:32:44 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 403 162 "-" "-" 0.023 BYPASS [07/Nov/2015:16:32:44 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.023 BYPASS [07/Nov/2015:16:32:45 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.025 BYPASS [07/Nov/2015:16:32:46 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" - BYPASS [07/Nov/2015:16:32:47 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 403 162 "-" "-" 0.022 BYPASS [07/Nov/2015:16:32:47 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.026 BYPASS [07/Nov/2015:16:32:48 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.027 BYPASS [07/Nov/2015:16:32:49 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" - BYPASS [07/Nov/2015:16:32:49 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 403 162 "-" "-" 0.023 BYPASS [07/Nov/2015:16:32:50 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-" 0.022 BYPASS [07/Nov/2015:16:32:51 +0700] dautu365.com "POST /wp-login.php HTTP/1.0" 200 3575 "-" "-"

And fail2ban ban him


The IP has just been banned by Fail2Ban after
3 attempts against 403block.

Here is more information about

% This is the RIPE Database query service.
% The objects are in RPSL format.
% The RIPE Database is subject to Terms and Conditions.
% See http://www.ripe.net/db/support/db-terms-conditions.pdf

% Note: this output has been filtered.
%       To receive output for a database update, use the "-B" flag.

% Information related to ' -'

% Abuse contact for ' -' is 'abuse@ovh.net'

inetnum: -
netname:        FR-OVH-20150522
descr:          OVH SAS
country:        FR
admin-c:        OTC2-RIPE
tech-c:         OTC2-RIPE
status:         LEGACY
mnt-by:         OVH-MNT
created:        2015-05-26T08:55:56Z
last-modified:  2015-05-27T15:52:47Z
source:         RIPE # Filtered
org:            ORG-OS3-RIPE

organisation:   ORG-OS3-RIPE
org-name:       OVH SAS
org-type:       LIR
address:        2 rue Kellermann
address:        59100
address:        Roubaix
address:        FRANCE
phone:          +333974531323
fax-no:         +33320200958
abuse-c:        AR15333-RIPE
admin-c:        GM84-RIPE
admin-c:        OTC2-RIPE
admin-c:        OK217-RIPE
mnt-ref:        OVH-MNT
mnt-ref:        RIPE-NCC-HM-MNT
mnt-by:         RIPE-NCC-HM-MNT
abuse-mailbox:  abuse@ovh.net
created:        2004-04-17T11:23:17Z
last-modified:  2015-03-24T14:19:23Z
source:         RIPE # Filtered

role:           OVH Technical Contact
address:        OVH SAS
address:        2 rue Kellermann
address:        59100 Roubaix
address:        France
admin-c:        OK217-RIPE
tech-c:         GM84-RIPE
tech-c:         SL10162-RIPE
nic-hdl:        OTC2-RIPE
abuse-mailbox:  abuse@ovh.net
mnt-by:         OVH-MNT
created:        2004-01-28T17:42:29Z
last-modified:  2014-09-05T10:47:15Z
source:         RIPE # Filtered

% Information related to ''

descr:          OVH
origin:         AS16276
mnt-by:         OVH-MNT
created:        2015-05-28T17:50:05Z
last-modified:  2015-05-28T17:50:05Z
source:         RIPE # Filtered

% This query was served by the RIPE Database Query Service version 1.82 (DB-1)