Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Rich Armstrong
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Rich Armstrong ( @richarmstrong )

Serving A Bypassable "Down For Maintenance" Page In ColdFusion 2021

By on
Tags:

In the vast majority of cases, updates to my ColdFusion blog can be made while the site is online. Sometimes, however, if those changes are not backwards compatible, or require too much cross-file coordination, there's no way that I can start making changes without causing errors in the user experience (UX). In such cases, I need to temporarily block access to the site using a "Down for Maintenance" page. But, I still need to access the site in order to monitor and test the changes. As such, this maintenance page needs to be conditionally bypassable. Luckily, all of this is really easy in ColdFusion.

There's No One-Size-Fits-All Maintenance Page

To be clear, there are many ways to implement a maintenance page in a web application. For example, at InVision, we manage our maintenance page at the outer-most ALB (Application Load Balancer). This way, we actually prevent traffic from entering most of our network.

You could also change the routing or virtual-host bindings at the web server level - think nginx, Apache, or Microsoft's Internet Information Service (IIS). This way, you're actually rendering a different "site" when users hit your domain.

In a ColdFusion application, you can override the page template in your onRequestStart() event-handler. This allows you to leverage the full-force of your ColdFusion business logic when rendering the maintenance page. But, it means that your ColdFusion application has to be boot-strappable; which it may not.

Which leads me to the approach that I am using, as outlined in this blog post. As you'll see below, I'm including the maintenance page template as the very first operation in my Application.cfc - a template which renders the maintenance page for the user and then aborts the rest of the request. This approach limits what we can do in the maintenance page (for example, there is no application scope). But, it also means that we don't have to worry about our ColdFusion application being in a "working state".

Including the Maintenance Page Template

This simple approach works by using the CFInclude tag to invoke the maintenance template as the very first operation inside my Application.cfc ColdFusion application component. I do this even before I define the application name or any of the application settings:

component 
	output = false
	hint = "I define the application settings and event handlers."
	{

	include "./maintenance.cfm";

	// Define application settings.
	this.name = "WwwBenNadelCom";
	this.applicationTimeout = createTimeSpan( 2, 0, 0, 0 );
	this.sessionManagement =  false;
	this.setClientCookies = false;

	// .... truncated for demo ....

}

To be clear, I do not leave this CFInclude in the code all the time. During normal website operation, this line would be commented-out. However, when I need to take the site offline in order to perform non-trivial updates, I'll uncomment the CFInclude statement and then redeploy the Application.cfc file.

This maintenance.cfm template is a stand-alone ColdFusion template that performs several actions:

  • Sends the correct HTTP Headers and status code for an unavailable website.

  • Renders the maintenance page.

  • Offers a cached version of the requested page to the user (via Google).

  • Aborts the request so that the request does not try to bootstrap - or depend on - the ColdFusion application.

  • Conditionally allows me to access the underlying ColdFusion application based on a simple Cookie value. This way, I can test the updates to the site even while the maintenance page is live.

Here's my current version of this template:

<cfscript>

	// Check cookies to see if the current request should bypass the maintenance page.
	// --
	// NOTE: We're not "securing" the active site - we simply hiding it from users while
	// it is undergoing some work. As such, this check here doesn't have to be very
	// effective - it just has to provide a nice user experience (UX).
	if ( ! compare( cookie?.earlyAccess, "PleaseToBeHavingEarlyAccess!" ) ) {

		exit;

	}

	// Google allows users to search for cached versions of pages. As a convenience, let's
	// link to the search results for the cached version of the requested page.
	searchUrl = "https://www.google.com/search?q=#encodeForUrl( "cache:https://www.bennadel.com#cgi.path_info#" )#";

</cfscript>
<!---
	Report to the browser that this page is not representative of the active application.
	And, that the client should check back in 30-min to see if the site is back online.
--->
<cfheader statusCode="503" statusText="Service Temporarily Unavailable" />
<cfheader name="Retry-After" value="#( 60 * 30 )#" />
<cfcontent type="text/html; charset=utf-8" />

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<title>
		Down for Maintenance - BenNadel.com
	</title>

	<link rel="shortcut icon" type="image/png" href="/favicon.png" />
	<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" />
	<style type="text/css">
		body {
			color: #333333 ;
			font-family: "nunito sans", arial, helvetica, sans-serif ;
			font-size: 1.2rem ;
			font-weight: 400 ;
			line-height: 1.5 ;
			margin: 20px auto 20px auto ;
			max-width: 650px ;
			padding: 0px 20px 0px 20px ;
			text-align: center ;
		}
		h1:before,
		h1:after {
			content: "\1F6A7" ; /* Construction sign emoji. */
		}
		p {
			margin-bottom: 30px ;
		}
		p.cache {
			font-weight: 700 ;
		}
		p.cache strong {
			background-color: #ffe05a ;
			display: inline-block ;
			padding: 0px 4px 0px 4px ;
			text-decoration: inherit ;
		}
		a {
			color: inherit ;
		}
		strong {
			white-space: nowrap ;
		}
	</style>
</head>
<body>

	<h1>
		Down for Maintenance
	</h1>

	<p>
		<strong>So Sorry</strong> &mdash; I am currently applying updates to my site that
		cannot be performed while the site is online. This should only take a few minutes.
		Thank you for your patience. Please check back shortly.
	</p>

	<p class="cache">
		<a href="<cfoutput>#searchUrl#</cfoutput>" target="blank">
			Try viewing the <strong>cached version</strong> of this page on
			<strong>Google</strong>
		</a>
		&rarr;
	</p>

	<p>
		Maintenance window started at <strong>6:05 AM EST</strong>.
	</p>

</body>
</html>

<!--- Don't let anything else in the request get processed. --->
<cfabort />

Notice that the very last thing in this CFML template is a CFAbort tag. This terminates the processing of the request. And, since this template is being processed as an include in the Application.cfc file, it means that it terminates the instantiation of the Application.cfc component - the component that is instantiated on every request to a ColdFusion page. This prevents the request from bootstrapping the underlying ColdFusion application, which means that we don't have to worry about any breaking-changes in our ColdFusion logic.

At this point, if I made a request to my ColdFusion blog without doing anything special with the cookies, I would see this page:

The down for maintenance page being rendered by the ColdFusion application server.

As you can see, the user gets the "Down for Maintenance" page. And, since this is happening as the first statement in the Application.cfc file, this is the page that will be rendered no matter which URL a user requests.

At this point, we can't depend on any logic in the underlying ColdFusion application - from this template's perspective, that application doesn't actually exist yet (hasn't been associated with the request). But, we can still depend on the native behavior of the ColdFusion application server. Which means that we can access the cgi scope; which, in turn, gives us access to the request information. In this case, I'm using the cgi.path_info to see what URL the user asked for. And then, I'm using that to information to provide a link to Google's cached version of the requested page.

Once I've confirmed that the maintenance page is indeed being rendered for the users, I then update my cookies to allow me to access the underlying ColdFusion application. At the top of the maintenance CFML template, I'm simply checking to see if a cookie-value exists; and, if so, I exit out of the maintenance template:

EditThisCookie Chrome extension being used to add the maintenance page bypass cookie.

Here, I'm using the Edit This Cookie Chrome extension to manually add the maintenance page bypass cookie to my current browser session. And, once this cookie is in place, my next request to the site bypasses the maintenance template and allows the request to enter the underlying ColdFusion application:

The underlying ColdFusion application being rendered because the down for maintenance template is being bypassed with the appropriate cookie value.

At that point, I can personally test the updates being made to the site even while everyone else is still seeing the down for maintenance page.

Once I'm done validating all the changes, I then comment-out the CFInclude statement at the top of the Application.cfc ColdFusion application component and then redeploy it. At this point, all incoming traffic will be directed to the ColdFusion application and all will be happy!

Again, I must emphasize that there are a thousand-and-one ways to accomplish something like this. This is just the approach that I use because it balances functionality with simplicity. This approach may not work for your situation.

That Cookie Isn't Very Secure

You might look at this approach and think it's not very secure. After all, couldn't anyone just add that cookie to their browser and bypass the maintenance page? Well, sure. But to what end? This maintenance page isn't here to secure anything - it's here to provide a nice user experience while the underlying application is being updated. If a user were to prematurely bypass this maintenance page, they wouldn't see anything "secret" - they'd probably just get some sort of runtime error.

Don't Override The Error Response Content

It's been a while since I've had to deal with this problem; but, I'm pretty sure that some web servers (such as IIS) will override error responses by default. Meaning, if your ColdFusion application returns a non-200 status code, the web server will ignore your content and serve up some static error page. This is a setting you can override in the web server - you can tell it to serve-up whatever content your ColdFusion application server returned.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel