One of the pains of being a website administrator is securing the login to the administrative portion of your website. Ideally, the website tells someone other than the true administrator to bugger off in a nice way. What better way for that to happen than for the website to tell the visitor: “That login? It’s gone, son…” HTTP response code 410 does that effectively. This post is part of the “.htaccess redirection fun with mod_rewrite” series. Dedicated to WordPress administrators, the techniques here are applicable to any access prevention use case that uses .htaccess and mod_rewrite.
“[G]one” comes from the meaning of the HTTP response code 410:
Indicates that the resource requested was previously in use but is no longer available and will not be available again. This should be used when a resource has been intentionally removed and the resource should be purged. Upon receiving a 410 status code, the client should not request the resource in the future. Clients such as search engines should remove the resource from their indices. Most use cases do not require clients and search engines to purge the resource, and a “404 Not Found” may be used instead.
Wikipedia
So how do we go about performing this magic with response code 410? First you have to determine where you want to log in from. Usually, as discussed in “Why is your fly open to the world?“, you only administer the website or server from a finite set of IP address. Determine what those IP addresses are, and how wide of a CIDR address space you want to log in from. I recommend the CIDR24 address space that contains your IP address. Once you have that address or address space, then it is a “simple” thing to do a test on the IP address or range to see if the querying IP address is not in the range. “It’s gone, son…”?
Response Code 410 Directives
The .htaccess stanza to do so (using a local WIFI router address space as example) is:
# BEGIN admin-user
RewriteEngine On
#--exclude "/wp-login.php from everyone but the home base address range--#
RewriteCond %{REMOTE_ADDR} !^192\.168\.0\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$
RewriteCond %{REQUEST_URI} ^/wp-login\.php
#--redirect all .php requests to 410 Gone--#
RewriteRule \.php$ - [G,L]
# END admin-user
BEGIN and END define the rule block. We must turn on the rewrite engine. Remember we have to enable mod_rewrite on Apache for any of this to work.
RewriteCond %{REMOTE_ADDR} !^192\.168\.0\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$
The first rule tests for the remote IP address or range specified in the regular expression. The exclamation point at the beginning of the expression negates it. The caret indicates the beginning of REMOTE_ADDR. Following is the IP address or range to check for. This is where you place your administrative IP address or address range. In this example we are using a CIDR24 address range. The term in parenthesis results from a GPT-4 query to test for the last byte in a CIDR24 IP address range, 0-255 inclusive. If you have multiple IP address or IP ranges use the “[OR]” RewriteCond flag at the end of the test case. The relationship of this rule to the next rule is logical “AND”.
RewriteCond %{REQUEST_URI} ^/wp-login\.php
The second rule tests for the magic word required to log into a WordPress site. That magic word always boils down to some variant of wp-login.php in the parameter REQUEST_URI. However, only if it is the first parameter after the / after the URL. Again, the caret indicates the beginning of the parameter. The “/” forces only triggering on a call to wp-login.php that would be effective, not something like “/cgi/wp-login.php” which should return a page not found error, 404.
RewriteRule \.php$ – [G,L]
The rewrite rule just returns the response code 410, “G”, for anything in the REQUEST_URI that ends in “.php”. This rule indicates it’s the last rule, “L”, Apache stops processing rules and responds to the request with the response code 410.
It’s hilarious watching the effectiveness of this in the access log. Botnets, one shot IP addresses that never come by again, everybody wants to log into WordPress. But seriously, guys, it’s gone…