Maintenance pages status codes and Lighttpd

I’ve noticed a few very broken maintenance page Lighttpd config examples around, including the one on the mod_magnet documentation page. They all manage to display the maintenance page ok, but they return a HTTP 200 status code to the client, rather than the more appropriate HTTP 503 code.

As with all 500 status codes, the 503 code is an error code but it signifies a temporary error. The client should try again later (in fact you can specify how much later using the Retry-After header).

A 200 code tells the client everything is normal and OK. So the user gets your nice maintenance page telling them of a temporary outage, whereas their browser gets told that everything is fine. Now this might not be a problem for a user, but if the client is a search engine or a caching proxy then it will assume the maintenance page is the new valid content for the request.

If the Google crawler hits your site when you have the maintenance page up, it will update its search index with your “we’re down for now” message, rather than your cash prizes blog content. Your page rank will drop, your fat Adsense cheque will diminish and you’ll have to go back to your regular nine to five job in the city with people you don’t like in clothes you hate wearing.

So, as you can see, it’s important to return the correct status code. Here’s how to do it with Lighty and mod_magnet:


Mod_magnet allows you to control request handling in Lighttpd using the Lua programming language (it replaces mod_cml and adds some bells and whistles). Here’s how to do a maintenance page with it:

Ensure the module is loaded:

server.modules += ("mod_magnet")

Tell it where to find your Lua script:

magnet.attract-physical-path-to = (server.docroot + "/magnet.lua")

Write a lua script to check for the maintenance page file and display it instead of the usual content if it exists.

if (lighty.stat(lighty.env["physical.doc-root"] .. "/maintenance-live.html")) then
                lighty.content = { { filename = lighty.env["physical.doc-root"] .. "/maintenance-live.html" } }
                lighty.header["Content-Type"] = "text/html"
        return 503

Create your maintenance page as maintenance.html in your document root. When you want it displayed, symlink it to maintenance-live.html and it will automatically be displayed.

I find it useful to be able to switch back and forth easily without restarts in and around the actual maintenance period but don’t need it day to day. So if you worry about the extra file stat operation (and the Lua) overhead during normal periods then just comment the Lua code out when you’re done with the maintenance feature for a while. mod_magnet will detect that you modified the Lua script and recompile it automatically.

You’ll notice that the return 503 actually returns a pre-generated HTML page as well as setting the status code. This extra bit of html gets tagged onto the end of your nice maintenance page, making it a bit ugly. You’ll just have to put up with this for now. I see this as a bug in Lighty which needs addressing. There should be some way to set a status code with Lua without returning the pre-generated html too.

Debug access

It might be convenient to disable the maintenance page for certain client IP addresses: if you’re upgrading the site, you’ll want to test it’s working yourself before disabling the maintenance page, right? I couldn’t figure out how to get the source address from within the Lua script, so I did it as a conditional in the Lighty config:

  $HTTP["remoteip"] != "" {
    magnet.attract-physical-path-to = ( server.document-root + "/magnet.lua" )

Using mod_magnet might be overkill for just this task, but if you’re already using it for other request processing stuff, then it’s ideal for this. Or maybe its convenience will be enough to sway you. Just remember to get the status code right!

See nixCraft for another example using PHP.


[…] support’. We got the idea to use mod_magnet like this from John Leach’s blog post on maintenance pages status codes and lighttpd, but we removed all logic from the LUA script and provided a workaround for a lighttpd […]

orchid says:

is there a way to selective doing above for a specific virtual host/site. there is another lua script running as following:
— little helper function
2 function file_exists(path)
3 local attr = lighty.stat(path)
4 if (attr) then
5 return true
6 else
7 return false
8 end
9 end
10 function removePrefix(str, prefix)
11 return str:sub(1,#prefix+1) == prefix..”/” and str:sub(#prefix+2)
12 end
14 — prefix without the trailing slash
15 local prefix = ”
17 — the magic ;)
18 if (not file_exists(lighty.env[“physical.path”])) then
19 — file still missing. pass it to the fastcgi backend
20 request_uri = removePrefix(lighty.env[“uri.path”], prefix)
21 if request_uri then
22 lighty.env[“uri.path”] = prefix .. “index.php”
23 local uriquery = lighty.env[“uri.query”] or “”
24 lighty.env[“uri.query”] = uriquery .. (uriquery ~= “” and “&” or “”) .. “url=” .. request_uri
25 lighty.env[“physical.rel-path”] = lighty.env[“uri.path”]
26 lighty.env[“request.orig-uri”] = lighty.env[“request.uri”]
27 lighty.env[“physical.path”] = lighty.env[“physical.doc-root”] .. lighty.env[“physical.rel-path”]
28 end
29 end
30 — fallthrough will put it back into the lighty request loop
31 — that means we get the 304 handling for free. ;)
32 — vim: set ft=lua sw=2 ts=2 sts=2 : —

how can I manage to get to work the 504 lua script with this one?

Leave a Reply