Reputation: 329
Okay - the logic of my brain is far from the logic of how code in a .htaccess file gets processed... to bad as it get frustrating sometimes when you try to achieve something "logical".
I would like to set a custom ErrorDocument when variable MAINTENANCE_MODE is set but it will simply not load the correct file. Is this because the If-directive is looked at earlier than the RewriteRules?
SetEnv MAINTENANCE_MODE 1
ErrorDocument 503 /msg/503_default.html
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{ENV:MAINTENANCE_MODE} 1
RewriteRule ^ - [E=MAINTENANCE_503:1]
</IfModule>
<If "%{ENV:MAINTENANCE_503} == '1'">
ErrorDocument 503 /msg/503_maintenance.php
RewriteRule !^503 - [R=503]
</If>
When it's totally not possible, which alternative route would be producing the best result?
Would that be to create a 503_default.php which can receive a value so it knows which custom message to show? If so, how do I set/pass that value onto the default 503 page?
Btw - this is just a simplified base version of what it will have to become. I also have other scenario's in which I want to serve a custom 503 message. But without a base there is not point in writing extended code :)
Upvotes: 3
Views: 260
Reputation: 329
Okay - with help of the input from MrWhite to this and my previous question I managed to land on a working solution with a custom ErrorDocument 503. Thanks @MrWhite!
The requirement
A custom 503 message for 3 specific situations:
The third reason might need some explanation. We (a community of whisky enthousiasts) buy casks and anyone in the community can buy a share of it through the shop, as long as it's in stock of course. We always release products at 1900 CE(S)T after announcing it first in the newsletter. So especially with popular/hard to get brands it can get busy real fast on the server as typically we have between 30 to 100 caskshares in a cask and the community has over 1.000 members these days. So this measure helps us to crash only at a later load and has helped us to reduce the occurrences of crashing the site from roughly 3 times a year to 0 (so far, in the last 3 years). As it's a profitless hobby initiative we just keep the webhosting costs under control as we can't justify to enlarge the server capacity for just that handful of times in a year.
The solution
The .htaccess file
Let's start with the .htaccess file. The (simplified) code I landed on is:
SetEnvIf ^ ^ GEO_BLOCK_MODE=1
SetEnvIf ^ ^ MAINTENANCE_MODE=0
SetEnvIf ^ ^ PRODUCT_RELEASE_MODE=1
ErrorDocument 503 /msg/503_handler.php
RewriteEngine On
# [0] off [1] on
RewriteCond %{ENV:GEO_BLOCK_MODE} =1
RewriteCond %{ENV:REDIRECT_STATUS} !=503
RewriteCond %{ENV:GEOIP_CONTINENT_CODE} ^(AF|AS|SA)$ [OR]
RewriteCond %{ENV:GEOIP_CONTINENT_CODE_V6} ^(AF|AS|SA)$ [OR]
RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^(AL|AM|AZ|BY|BG|GE|KZ|MD|MK|RO|RU|TR)$ [OR]
RewriteCond %{ENV:GEOIP_COUNTRY_CODE_v6} ^(AL|AM|AZ|BY|BG|GE|KZ|MD|MK|RO|RU|TR)$
RewriteCond %{REQUEST_URI} ^/shop/?.* [NC]
RewriteRule ^ - [R=503,E=503_MODE:GEO_BLOCK]
# [0] off [1] this morning | afternoon | evening/night [2] entire day [3] this weekend
RewriteCond %{ENV:MAINTENANCE_MODE} !=0
RewriteCond %{ENV:REDIRECT_STATUS} !=503
RewriteRule ^ - [R=503,E=503_MODE:MAINTENANCE]
# [0] off [1] on
RewriteCond %{ENV:PRODUCT_RELEASE_MODE} =1
RewriteCond %{TIME_HOUR}%{TIME_MIN} >1844
RewriteCond %{TIME_HOUR}%{TIME_MIN} <1946
RewriteCond %{DOCUMENT_ROOT}/.productrelease -f
RewriteCond %{REQUEST_URI} ^/(forum|etcetera)(/.*)?$ [NC]
RewriteRule ^ - [R=503,E=503_MODE:PRODUCT_RELEASE]
With GEO_BLOCK_MODE
set to 1
the section is enabled.
Then we check if the page is not a redirected page with code 503 before we continue with the other conditions we want to be applicable to the situation.
At the end we redirect to 503 and set a 503_MODE
$_SERVER variable we can retrieve in our 503_handler.php via REDIRECT_503_MODE
.
The other 2 sections are setup in the same fashion and with regards to the PRODUCT_RELEASE_MODE
section... there are 2 cronjobs running on the server:
The 503_handler.php file
The code in our .htaccess leads us to a custom 503 handler file. Which I have setup as follow to suit my needs:
<?php
$minutes = 15;
$retry_after = date("D, d M Y H:i:s T", strtotime('+' . $minutes . ' minutes'));
$refresh = $minutes * 60;
header('HTTP/1.1 503 Service Unavailable', true, 503);
header('Retry-After: '. $retry_after );
header('Refresh: '. $refresh .'; url=' . $_SERVER["REQUEST_URI"] );
switch( $_SERVER['REDIRECT_503_MODE'] ) {
case "GEO_BLOCK":
$class = "bg-yellow";
$title = "Service Unavailable";
$text = "...";
break;
case "MAINTENANCE":
$class = "bg-blue";
$title = "Maintenance Window";
switch( $_SERVER['REDIRECT_MAINTENANCE_MODE'] ) {
case 2: # today, entire day
$text = "...";
break;
case 3: # this weekend
$text = "...";
break;
# case 1: # dynamic: morning | afternoon | evening/night
default:
$hour = date('G');
if( $hour >= 6 && $hour < 12 ) {
$text = '...morning';
} elseif( $hour >= 12 && $hour < 18 ) {
$text = '...afternoon';
} else {
$text = '...evening/night';
}
break;
}
break;
case "PRODUCT_RELEASE":
$class = "bg-blue";
$title = "Scheduled Downtime";
$text = "...";
break;
default:
$class = "bg-purple";
$title = "Service Unavailable";
$text = "...";
break;
}
?><!DOCTYPE html>
<html>
...
Okay - this is just the bare bones of my custom 503_handler.php file but it gives you a clear idea on how to set it up and how to capture the values we've setup / generated within our .htaccess code.
That's it
And that is it.
I now have a working solution to show custom 503 messages for the 3 defined satiations and best of all:
Sweet!
Upvotes: 0
Reputation: 45958
A 503 (Service Unavailable) basically IS "maintenance mode". Usually, the only time a 503 is triggered is when the site is "under maintenance", so conditionally having two different 503 documents is rarely a requirement. (Unless perhaps you have different subsites in the same file space?)
If you specifically wanted to deliver a different response when the MAINTENANCE_MODE
env var is set then I would simply check for this in the 503.php
file itself (using PHP) and act accordingly.
However, to address your specific problem...
RewriteCond %{ENV:MAINTENANCE_MODE} 1 RewriteRule ^ - [E=MAINTENANCE_503:1]
The problem here is that you are setting the MAINTENANCE_MODE
env var using SetEnv
(mod_setenv). SetEnv
is processed too late for mod_rewrite (and <If>
expressions) to pick up. Since mod_rewrite is processed early, MAINTENANCE_MODE
is not set when this rule is processed, so MAINTENANCE_503
is never set.
As briefly mentioned in my earlier answer, you would need to use SetEnvIf
(mod_setenvif) instead to set the env var since this is processed very early - before mod_rewrite (and before <If>
expressions are evaluated). But note the different syntax (since SetEnvIf
is primarily used to set env vars conditionally, hence the If
).
Although I'm not sure what the intent is here, as there doesn't seem to be much point in setting another "maintenance" env var to "1" when you already have a "maintenance" env var set to "1"? (A hacky attempt to try and get the <If>
expression to play ball perhaps?)
Aside: You don't need the <IfModule>
wrappers - unless this is intended to work on systems that don't have mod_rewrite enabled. Although not all the mod_rewrite directives are contained in a <IfModule mod_rewrite.c>
wrapper anyway, so that kinda defeats the point. See my answer to the following question on the Webmasters stack: https://webmasters.stackexchange.com/questions/112600/is-checking-for-mod-write-really-necessary
So, you could do something like this:
SetEnvIf ^ ^ MAINTENANCE_MODE=1
ErrorDocument 503 /msg/503_default.html
<If "%{ENV:MAINTENANCE_MODE} == '1'">
ErrorDocument 503 /msg/503_maintenance.php
RewriteEngine On
RewriteCond %{ENV:REDIRECT_STATUS} !=503
RewriteRule ^ - [R=503]
</If>
Note that the <If>
expression tests MAINTENANCE_MODE
directly, not MAINTENANCE_503
. (I couldn't see the point of MAINTENANCE_503
?)
This would trigger a 503 for every URL, including direct requests to /msg/503_maintenance.php
itself. The condition that checks against the REDIRECT_STATUS
env var avoids a rewrite loop in this scenario.
However, as mentioned earlier, is having multiple 503 ErrorDocuments defined in Apache really necessary? If not then you would check for the env var in the mod_rewrite rule itself. For example:
SetEnvIf ^ ^ MAINTENANCE_MODE=1
ErrorDocument 503 /msg/503_maintenance.php
RewriteEngine On
RewriteCond %{ENV:MAINTENANCE_MODE} =1
RewriteCond %{ENV:REDIRECT_STATUS} !=503
RewriteRule ^ - [R=503]
(Although, an often handy way to trigger "maintenance mode" is simply the presence of an arbitrary file, eg. ".maintenance-mode-ENABLED", which then avoids the need to have to edit .htaccess
to set an env var - which itself carries a certain "risk".)
And, if you are enabling "maintenance mode" and sending a 503, you should really be sending a Retry-After
HTTP response header as well. Again, see the following question (and my answer) on the Webmasters stack:
https://webmasters.stackexchange.com/questions/55635/website-is-under-maintenance-how-to-restrict-access
Upvotes: 3