Sitecore with Azure Front Door and Your New Frenemy, The X-FORWARDED-PORT Header
If you’re using Sitecore on AKS or Azure App Services, you may be using Azure Front Door as a reverse proxy for your site. Azure Front Door allows you to register domains and route traffic to backends (such as AKS). It also comes with some niceties such as built-in caching via Azure CDN POPs, SSL termination, automatic certification generation and renewals, and latecy-based routing among others. But you’re not here to learn about Front Door, you can go read Microsoft documentation for that. You want to know how to use Azure Front Door with Sitecore.
Fortunately, for the most part, Azure Front Door works great with Sitecore! You can even use it for SSL termination, then send the HTTP request over Private Link to your AKS cluster. Great, blog post over, see you next time.
Except for one thing…
That one thing is the X-FORWARDED-PORT
header, or in this case, the lack thereof. When you enable load balancing for Sitecore, there are several settings that get set. Here’s the Sitecore.LoadBalancing.config.example
file that comes with Sitecore:
<?xml version="1.0" encoding="utf-8" ?>
<!--
Purpose: This include file enables SSL offloading feature so that the system could take into account X-FORWARDED headers added by the load-balancer.
Please consult your Sitecore partner before enabling this include file.
Enabling this include file without taking into account the specifics of your solution can have unexpected consequences.
To enable this include file, rename it to have a ".config" extension.
-->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
<sitecore role:require="Standalone or ContentManagement or ContentDelivery">
<settings>
<!-- SITECORE LOADBALANCING ENABLED
Defines whether the SSL offloading feature is enabled
Default value: true
-->
<setting name="Sitecore.LoadBalancing.Enabled" value="true" />
<!-- SITECORE LOADBALANCING HOSTHEADER
Defines the value indicating from which request header the host should be taken during the SSL offloading
Default value: X-FORWARDED-HOST
-->
<setting name="Sitecore.LoadBalancing.HostHeader" value="X-FORWARDED-HOST" />
<!-- SITECORE LOADBALANCING SCHEMEHEADER
Defines the value indicating from which request header the scheme should be taken during the SSL offloading
Default value: X-FORWARDED-PROTO
-->
<setting name="Sitecore.LoadBalancing.SchemeHeader" value="X-FORWARDED-PROTO" />
<!-- SITECORE LOADBALANCING PORTHEADER
Defines the value indicating from which request header the port should be taken during the SSL offloading
Default value: X-FORWARDED-PORT
-->
<setting name="Sitecore.LoadBalancing.PortHeader" value="X-FORWARDED-PORT" />
</settings>
</sitecore>
</configuration>
You’ll see that several settings get set that indicate which headers contain data from the load balancer that get passed on.
X-FORWARDED-HOST
gives you the originally requested hostnameX-FORWARDED-PROTO
gives you the scheme for the original request, whetherhttps
orhttp
. If you’re dealing with the internet, this had better behttps
.X-FORWARDED-PORT
gives you the port number of the original request.
Sitecore uses these headers to do things like correctly generate URLs. Seems pretty important. Azure Front Door passes along these headers to the backend from the original request, as you’ll see here in the documentation.
You will notice that one of the three headers is missing - the X-FORWARDED-PORT
header. The X-FORWARDED-PORT
header, while convenient, isn’t considered a defacto standard header unlike X-FORWARDED-HOST
(MDN) or X-FORWARDED-PROTO
(MDN). That means there are load balancers and reverse proxies, such as Azure Front Door, that don’t send the header.
This became glaringly obvious when Sitecore changed up their implementation in 10.3 of resolving the start URL when a user logs in to take into account these headers, which, it should. However, it tries to build the URL like so:
SSL terminated URL: http://internalhost:80/sitecore
- If populated, replace the host with the hostname defined in
X-FORWARDED-HOST
. Otherwise use the original hostname.
Result:http://coolsite.com:80/sitecore
- If populated, replace the scheme with the value defined in
X-FORWARDED-PROTO
. Otherwise use the original scheme.
Result:https://coolsite.com:80/sitecore
- If populated, replace the port with the port defined in
X-FORWARDED-PORT
. Otherwise use the original port.
Result:https://coolsite.com:443/sitecore
You might see the problem already. If there’s no value in the X-FORWARDED-PORT
header because none was passed along from the reverse proxy, you wind up with https://coolsite.com:80/sitecore
and browsers very much do not like you mixing https
and port 80. And this, my dear readers, is what I spent the last couple of weeks troubleshooting.
The proper solution is that the logic should fallback to the default port number based on the scheme defined in X-FORWARDED-PROTO
, so if X-FORWARDED-PROTO
was https
, it should fall back to 443 as the port rather than the original port number of the request.
Unfortunately we don’t have the luxury of proper solutions, so we have to make our own luck. This is where the magic of the Azure Front Door rules engine comes into play. We can add our own X-FORWARDED-PORT
header. Hop into the rule sets section and add this rule:
Whenever a request hits Azure Front Door that is an HTTPS request, append the X-FORWARDED-PORT
header with the value of 443 to the request to the backend. Then, everyone is all nice and happy and Sitecore will stop throwing some crazy URLs at you.