As a general rule, I try to avoid painful experiences, but in this case the experience was thrust upon me. I was given two Concrete5 websites to migrate to Azure.

I'm comfortable running my own Linux server, but I'm the only one on my team that is so. Running PHP on Windows is best avoided if possible, and wasn't cost-effective when compared to running as App Services. We had tested running one of the sites on a Linux-based App Service versus a Windows-based App Service, and found the Linux-based service had a significant performance advantage, as measured by response time to the browser.

While we're on the subject of performance, I did not find the performance of Concrete5 to be satisfactory. Both sites are pretty basic brochureware. Some text, images, navigation; a single "contact us" form. Yet, reviewing the database query log for a single page showed 762 database queries. Seven-hundred-sixty-two. Individual queries. One page requested, one time. The action to take here, proposed on the Concrete5 forums and elsewhere by developers building sites with C5, is to enable caching. This does produce a snappy site, as pages are simply served from an on-disk cache instead of constructed from data, but this means you've got to tussle with reconstructing the cache as part of your publishing workflow. I've not had similar problems using WordPress, and maybe either the sites I was given (from two different developers) were poorly implemented, or they're two different use-cases, but either way I'd recommend WP over C5 in cases like this.

Single-page request queries in a Concrete5 site 

In the end though, the fault did not lie solely with Concrete5, Microsoft's Azure cloud service was also to blame.

What was happening, is that when a request came in, C5 was checking to see if the site was being served over HTTPS; and it was. Sort of. The requests were being made to the server and responses back to the client over HTTPS. However, behind the scenes, Azure accepted our request and then via its Application Request Routing scheme, routes the request to our code. As part of that proxying, it changes to HTTP.

An HTTPS header is not set
An HTTP_X_ARR_SSL header is set to the SSL info
An HTTP_X_FORWARDED_PROTO is set to the value "https"
A REQUEST_SCHEME header is set to "http"
A SERVER_PORT header is set to "80"

Headers coming in to a Linux Azure Web Service

If your application is checking for a header other than HTTP_X_FORWARDED_PROTO, then it will not correctly detect the URL scheme the request was made via. This is not clearly documented by Microsoft; I happened upon it in a Stack Overflow answer: (https://stackoverflow.com/a/38726543/248917).

Concrete5 builds page content URLs using the scheme and domain info from the incoming request. It checks for an HTTPS header, only checks the HTTP_X_FORWARDED_PROTO in the event a "trusted proxy" is set, and uses that to write the scheme for URLs on the page (see /concrete/vendor/symfony/http-foundation/Request.php). Personally, I'd prefer if URLs were written to the page scheme-less; forcing https-everywhere at the site level would enforce security.

Setting up a C5 site on an Azure Web Service resource with an SSL binding causes pages to be served back to the client over HTTPS, but URLs on the page, including form actions, are written with an HTTP scheme; with an SSL-rewrite rule on the server, C5 forms will not be processed correctly - you won't be able to log in to the admin section, for example.

My solution, while probably not the best, was to add the following code to index.php in the site root:

if(isset($_SERVER['HTTP_X_ARR_SSL'])) {
    $_SERVER['HTTPS'] = 'on';
}