It’s common when moving from one version of your application to another that you will want to maintain all of the SEO cred you have built up while simultaneously moving to a new url syntax. To do this people usually reach for mod_rewrite with apache or nginx for which there is quite a bit of documentation on this topic. Unfortunately the same can’t be said for rewriting and 301 redirecting when using HAProxy.

I have a rather common use case. I plan on moving this blog from Tumblr to Ghost using the Ghost Juju charm and the HAProxy charm to handle load balancing, reverse proxy and rewriting and redirecting the old Tumblr style urls to the Ghost url format.

Using mod_rewrite you would likely write something similar to the following to handle rewriting the url to the new syntax and redirecting with a 301 response code:

RewriteEngine On  
RewriteRule ^/post/\d+/(.+)/? http://example.com/$1  [R=301,L]

HAProxy however doesn’t have a single rule for rewrite and redirect instead we have to combine reqrep, to rewrite the url, and redirect, to handle the actual redirection.

Assume the following front and backend configurations:

frontend haproxy-0-80
    bind 0.0.0.0:80
    default_backend haproxy_service

backend haproxy_service
    balance leastconn
    cookie SRVNAME insert
    server ghost-0-2368 10.0.3.220:2368 maxconn 100 cookie S0 check

In order to rewrite the url we first need to add the rewrite into the frontend:

frontend haproxy-0-80
    bind 0.0.0.0:80
    default_backend haproxy_service
    reqrep ^([^\ :]*)\ /post/\d+/(.+)/?     \1\ /\2

This will rewrite the old Tumblr style url format to the new Ghost style url format and pass that url off to the Ghost webserver. If you’re ok with the user still seeing and using the old url style then you can stop here. Both the real Ghost url format and the old Tumblr style url format will work. If however you want to tell the users and any search engines that the old url is no longer valid and to use a new one instead we need to add the redirect rule:

frontend haproxy-0-80
    bind 0.0.0.0:80
    default_backend haproxy_service
    reqrep ^([^\ :]*)\ /post/\d+/(.+)/?     \1\ /\2
    redirect prefix / code 301

The HAProxy redirect syntax requires us to specify what kind of redirect we want to occur. The options are ‘location’, ‘prefix’, and ‘scheme’ none of these truly fit redirecting an old to new url. Fortunately we can trick HAProxy into doing just what we want by telling it we want to redirect to change the prefix of the url and passing / as the url to prefix along with the code we want to send, 301.

We aren’t quite done. If we leave this as is it will redirect every rule, including the Ghost url formatted ones which will put it into a redirect loop. In order to fix this we need to create an access control list to only redirect the old urls:

frontend haproxy-0-80
    bind 0.0.0.0:80
    default_backend haproxy_service
    acl old_url path_beg /post
    reqrep ^([^\ :]*)\ /post/\d+/(.+)/?     \1\ /\2
    redirect prefix / code 301 if old_url

To the frontend we added a new acl rule called “old_url” which returns true if the path begins with /post. We then add the conditional ‘if old_url” to the redirect rule and we’re done. After restarting the HAProxy service you’ll be able to use the old url structure and be 301 redirected to the new Ghost syntax urls which also remain functional.

In trying to resolve this issue I spent many hours reading the HAProxy documentation, reading blog posts and testing. I Even created a serverfault question which I have now updated with the solution so I hope that this post will save others a bunch of time. As always if you have any questions or comments please comment below or mention me on Twitter @fromanegg.