End-to-End - Setup free SSL certificate to secure Azure Web App with HTTPS

It is 2019 now, and HTTP is considered as “not secure”, and HTTPS is the default. This is a end-to-end tutorial of how to setup SSL certificate to secure Azure Web App with HTTPS. It is based on “Let’s Encrypt” and “letsencrypt-webapp-renewer”. “Let’s Encrypt” is a free certificate solution, and “letsencrypt-webapp-renewer” is a great automation tool for certificate installation. It is based on another tool “letsencrypt-siteextension”. The differences are,

  • “letsencrypt-webapp-renewer” does not require an Azure Storage Account, but “letsencrypt-siteextension” does.
  • “letsencrypt-webapp-renewer” is a WebJob, and can run on a different Web App from the Web App to install certificate. And it can manage multiple Web Apps’ certificates as well. “letsencrypt-siteextension” is a Website extension, and can only run with the Web App which needs certificate.

So here “letsencrypt-webapp-renewer” is used.

What is “Let’s Encrypt”

Let’s Encrypt” is a popular certificate authority, it can issue SSL certificate for free, and currently providing certificates for more than 115 million websites. Since July 2018, the Let’s Encrypt root, ISRG Root X1, is directly trusted by Microsoft products. So currently its root is now trusted by all mainstream root programs, including Microsoft, Google, Apple, Mozilla, Oracle, and Blackberry.

Annotation 2019-02-09 155224

However, its free certificate expirees in every 3 months (90 days), not yearly. So a automation process will  be setup to renew and install the certificates.

Setup Active Directory and App Registration

In Azure portal, go to the Active Directory, add a new App Registration:

Annotation 2019-02-09 073404_thumb

image

Save its application id, later it will be used as “client id”:

image

Then go to Certificates & secretes, add a client secrete, and save it:

image

Setup Resource Group

Go to resource group, In Access Control, add the above App Registration as contributor:

image

Setup Azure Web App (aka Azure App Service Website)

This article assumes an existing Azure Web App. If you do not have one yet, it is very straightforward to create one from the Azure portal. Then you can deploy your website to that Azure Web App.

Annotation 2019-02-09 020852_thumb

Save the subscription id and resource group name for later usage.

Only Basic (B1+) and above pricing tiers support SSL. The free tier (F1) and the cheapest Shared tier (D1) does not support SSL, and Microsoft has declined to enable this feature for Shared (D1). If you have a F1 or D1 web app, go to “Scale up” in the Azure portal, change the pricing tier to B1 and above, which is more than 3 times of the Shared (D1) price.

Annotation 2019-02-09 072255_thumb

Setup custom domain

Shared pricing tier and above support custom domain. Follow the guidelines in the portal to setup your domain.

Annotation 2019-02-09 064042_thumb

To verify your domain ownership, Let’s Encrypt requests your-domain.com/.well-known/acme-challenge/{longId}. For example: hanziyuan.net/.well-known/acme-challenge/mftvrU2brecAXB76BsLEqW_SL_srdG3oqTQTzR5KHeA.

Enable HTTPS in ASP.NET Core website

In your ASP.NET Core website, you may want to enable HSTS, and redirect HTTP request to HTTPS:

public class Startup
{
    public void ConfigureServices(IServiceCollection services) // Container.
    {
        if (this.environment.IsProduction())
        {
            services.AddHttpsRedirection(options => options.HttpsPort = 443);
        }

        // Other configuration.
    }

    public void Configure(IApplicationBuilder application, ILoggerFactory loggerFactory, IAntiforgery antiforgery, Settings settings) // HTTP pipeline.
    {
        if (this.environment.IsProduction())
        {
            application.UseHsts();
            application.UseHttpsRedirection(); 
        }

        // Other configuration.
    }
}

You also want to look up the hyperlinks and resources (images, etc.), and replace their URIs with HTTPS.

Automation with letsencrypt-webapp-renewer

letsencrypt-webapp-renewer is a console application. It works as a WebJob of Azure Web App, and automatically install/renew Let’s Encrypt certificate for your Azure Web App.

Create a separate Web App for automation

You can install SSL on one Azure Web App, and run letsencrypt-webapp-renewer as WebJob of the same Web App, or a different Web App. As the author Ohad Schneider pointed out in the comments, it is highly recommended to have the run letsencrypt-webapp-renewer as WebJob of a separate Web App, because:

  • You can use it to manage multiple Web Apps.
  • WebJob is just a console application, its files (*.exe, *.dll, etc.) are deployed to your Web App’s App_Data directory (e.g. App_Data/jobs/triggered/letsencrypt/). So a WebJob can be silently deleted when you deploy/publish your website.

In this tutorial, I run letsencrypt-webapp-renewer as WebJob of a separate Web App. This automation Web App is created under the same App Service plan. Since Azure charges per App service plan, I do not need to pay additional cost for this automation web App.

image

Add application settings

Download the setup PowerShell script from https://github.com/ohadschn/letsencrypt-webapp-renewer/blob/master/OhadSoft.AzureLetsEncrypt.Renewal/Scripts/Set-LetsEncryptConfiguration.ps1, and run:

PS D:\User\Desktop> .\Set-LetsEncryptConfiguration.ps1 -LetsEncryptSubscriptionId e09d69aa-afa1-4db3-aea3-ca58cc2d82ee -LetsEncryptResourceGroup etymology -LetsEncryptWebApp etymology-letsencrypt -SubscriptionId e09d69aa-afa1-4db3-aea3-ca58cc2d82ee -ResourceGroup etymology -WebApp etymology -ServicePlanResourceGroup etymology -TenantId dixinyanlive.onmicrosoft.com -ClientId 9ca16da2-9252-4a55-8c99-b41d458d7fc4 –ClientSecret ‘****’ -Hosts hanziyuan.net -Email dixinyan@live.com
Signing in to Azure Resource Manager account (use the account that contains your Let's Encrypt renewal web app)...


Account          : dixinyan@live.com
SubscriptionName : Visual Studio Enterprise
SubscriptionId   : e09d69aa-afa1-4db3-aea3-ca58cc2d82ee
TenantId         : 31a11410-e324-47a1-bbc4-9884031e3b14
Environment      : AzureCloud

Setting context to the Let's Encrypt subscription ID...

Name               : [dixinyan@live.com, e09d69aa-afa1-4db3-aea3-ca58cc2d82ee]
Account            : dixinyan@live.com
Environment        : AzureCloud
Subscription       : e09d69aa-afa1-4db3-aea3-ca58cc2d82ee
Tenant             : 31a11410-e324-47a1-bbc4-9884031e3b14
TokenCache         : Microsoft.Azure.Commands.Common.Authentication.AuthenticationStoreTokenCache
VersionProfile     :
ExtendedProperties : {}

Loading existing Let's Encrypt web app settings...
Copying over existing app settings...
Adding new settings...
Setting 'subscriptionId' to 'e09d69aa-afa1-4db3-aea3-ca58cc2d82ee'...
Setting 'resourceGroup' to 'etymology'...
Setting 'servicePlanResourceGroup' to 'etymology'...
Setting 'tenantId' to 'dixinyanlive.onmicrosoft.com'...
Setting 'clientId' to 'letsencrypt'...
Setting 'hosts' to 'hanziyuan.net'...
Setting 'email' to 'dixinyan@live.com'...
Value not provided for app setting 'useIpBasedSsl' - skipping...
Value not provided for app setting 'rsaKeyLength' - skipping...
Value not provided for app setting 'acmeBaseUri' - skipping...
Setting 'renewXNumberOfDaysBeforeExpiration' to '-1'...
Copying over existing connection strings...
Adding new connection string...
Updating settings...

SiteName                  : etymology-letsencrypt
State                     : Running
HostNames                 : {etymology-letsencrypt.azurewebsites.net}
RepositorySiteName        : etymology-letsencrypt
UsageState                : Normal
Enabled                   : True
EnabledHostNames          : {etymology-letsencrypt.azurewebsites.net, etymology-letsencrypt.scm.azurewebsites.net}
AvailabilityState         : Normal
HostNameSslStates         : {etymology-letsencrypt.azurewebsites.net, etymology-letsencrypt.scm.azurewebsites.net}
ServerFarmId              : /subscriptions/e09d69aa-afa1-4db3-aea3-ca58cc2d82ee/resourceGroups/etymology/providers/Micr
                             osoft.Web/serverfarms/etymology
LastModifiedTimeUtc       : 2/10/2019 10:43:42 PM
SiteConfig                : Microsoft.Azure.Management.WebSites.Models.SiteConfig
TrafficManagerHostNames   :
PremiumAppDeployed        :
ScmSiteAlsoStopped        : False
TargetSwapSlot            :
HostingEnvironmentProfile :
MicroService              :
GatewaySiteName           :
ClientAffinityEnabled     : True
ClientCertEnabled         : False
HostNamesDisabled         : False
OutboundIpAddresses       : 207.46.144.46,207.46.144.85,207.46.144.91,207.46.148.246
ContainerSize             : 0
MaxNumberOfWorkers        :
CloningInfo               :
ResourceGroup             : etymology
IsDefaultContainer        :
DefaultHostName           : etymology-letsencrypt.azurewebsites.net
Id                        : /subscriptions/e09d69aa-afa1-4db3-aea3-ca58cc2d82ee/resourceGroups/etymology/providers/Micr
                             osoft.Web/sites/etymology-letsencrypt
Name                      : etymology-letsencrypt
Location                  : East Asia
Type                      : Microsoft.Web/sites
Tags                      :

Let's Encrypt settings updated successfully

PS D:\User\Desktop>

This adds a bunch of settings and a connection string to the automation Web App, which will be read by the WebJob:

image

Setup and run WebJob

Download the latest release of letsencrypt-webapp-renewer, which is a zip file with everything packed. Upload it as a Triggered WebJob of the automation Web App:

image

As mentioned by the official document, “The recommended Let's Encrypt renewal period is 60 days, so you could use a CRON expression that fires once every two months, for example: 0 0 0 1 1,3,5,7,9,11 *.”

Now manually start the WebJob:

image

When it’s done, the SSL certificate is installed/renewed for the Azure Web App. And you are good to go with HTTPS: https://hanziyuan.net. You can also click the Logs button to view the details:

Annotation 2019-02-09 071735_thumb

This is useful for troubleshooting. For example, if your Web App is Shared (D1) pricing tier, it will fail with detailed info in the logs https://etymology.scm.azurewebsites.net/vfs/data/jobs/triggered/letsencrypt/201902081828412089/output_log.txt:

[02/08/2019 18:29:11 > 021587: INFO] AzureLetsEncryptRenewer.exe Error: 0 : Encountered exception: Microsoft.Rest.Azure.CloudException: Cannot enable SNI SSL for a hostname 'hanziyuan.net' because current site mode does not allow it.
[02/08/2019 18:29:11 > 021587: INFO] at Microsoft.Azure.Management.WebSites.WebAppsOperations.<BeginCreateOrUpdateWithHttpMessagesAsync>d__208.MoveNext()
[02/08/2019 18:29:11 > 021587: INFO] --- End of stack trace from previous location where exception was thrown ---

Once the WebJob finishes running, your Web App is secured with SSL, and HTTP requests are redirected to HTTPS.

image

And this is your certificate issued by Let’s Encrypt:

image

Setup notification with SendGrid

These steps are optional and just for WebJob notification. In Azure portal, create a free SendGrid account:

Annotation 2019-02-09 074622_thumb

Then go to its settings, copy the user name.

Annotation 2019-02-09 074843_thumb

Use the user name and password to log on SendGrid – https://sendgrid.com, then go to Settings, create an API key:

Annotation 2019-02-09 075120_thumb

Then in Azure portal, add a connection string “letsencrypt:SendGridApiKey” to Web App, then you are good to go:

Annotation 2019-02-09 075707_thumb

Setup notification with Zapier

These steps are optional and just for WebJob notification. In Azure portal, go to the Web App’s Properties, copy its deployment trigger URL. Then go to Zapier https://zapier.com, use that URL to create a Azure Web Apps Trigger with new Triggered WebJob run:

Annotation 2019-02-09 080358_thumb

Then setup email action:

Annotation 2019-02-09 081318_thumb

30 Comments

  • Looks great, very thorough!

    Some comments:
    1. I highly recommend against running the renewal WebJob on the same web app to be renewed, as there are various scenarios that will get your WebJob (silently) deleted. For example, turning on "Delete Existing Files" when publishing (which I personally do all the time). I basically created this WebJob for the express purpose of NOT installing it on the same web app - otherwise I would have just used the original https://github.com/sjkp/letsencrypt-siteextension. For example see: https://github.com/sjkp/letsencrypt-siteextension/issues/34 (there are several others),
    2. You should have "Always On" enabled, otherwise your CRON expression won't be reliable and you could miss the renewal window. Note that in order to have "Always On" you'll need a plan where you own the VM (Basic and up), in which case you won't be paying for extra web apps anyway (as they will all share your dedicated VM).
    3. Note that my WebJob is mostly a wrapper for the logic performed by https://github.com/sjkp/letsencrypt-siteextension which deserves most of the credit :)

  • Ohad, thanks for pointing out these. I agree with you the WebJob should be running on a separate Web App. I run it on the same Web App because I only have one Web App on Azure, and I do not want to pay for another Web App ($384 per year for another B1 Web App, which support “Always on” for WebJob). I have updated the contents for these 3 issues. Thank you!

  • Hi Dixin, thank you for updating the content with my comments.
    There is still one point though that one of us might be missing.
    Your tutorial shows that you have "Always on" enabled, meaning you are on the Basic App Service Plan or above (BTW, you don't have to have it on for WebJobs to fire per se as the article suggests, it just won't be reliable as your app could be unloaded at the time the CRON job should be triggered, see: https://docs.microsoft.com/en-us/azure/app-service/web-sites-configure#general-settings)
    This mean you have your own VM and you can open an *unlimited* number of Web Apps on that App Service Plan, for free. Of course you are only constrained by the resources of your VM, but I think I read that light it can hosts hundreds if not thousands of sites (depending on the load, VM SKU and instance count, whether Always On is enabled, etc).
    Anywa, creating a new dedicated cert renewal Web App on the same App Service Plan will not cost you 384$ - it will cost you 0$
    For more information see: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans

  • Hi Ohad, Thanks for the discussion. For years I mistakenly thought App Service plan defines the resources for EACH app, and Azure charges customer for each app. Now I realized App Service plan defines resources for ALL apps, and Azure charges customer for each App Service plan. Thank you for the correction! I always had the wrong impression of charging per app, because I worked for Azure SQL Database, and we charge customers per database. I have updated the contents to use a separate Web App for automation. Thank you. -Dixin

  • Glad I could help!
    I'll add your guide to the letsencrypt-webapp-renewer README soon.

  • does this software can run on Linux based azure web app?

  • I'm trying to do this but I get this error when running the script:
    **************************************************************************************
    C:\users\rayna\desktop\Set-LetsEncryptConfiguration.ps1 : The term 'Login-AzureRmAccount' is not recognized as the
    name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was
    included, verify that the path is correct and try again.
    At line:1 char:1
    + .\Set-LetsEncryptConfiguration.ps1 -LetsEncryptSubscriptionId e0a11e6 ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (Login-AzureRmAccount:String) [Set-LetsEncryptConfiguration.ps1], Comman
    dNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException,Set-LetsEncryptConfiguration.ps1
    ********************************************************************************************************************
    I've searched everything I could find but no help. I've setup my certificates so far using “letsencrypt-siteextension” and would love to put them all under yours.

    Any ideas?

    Thanks, Ray

  • This is a really useful and thorough walkthrough, and by far the best means I've found of accomplishing this goal anywhere to date. Thank you for simplifying what should be a much smoother process.

    I'm working on a Blazor app with ASP.NET Core 3.0, and I'm wondering, based on the contents of the rest of your blog, if you might know what would be necessary to enable this on the preview of the new version of ASP..NET core.. In particular, it looks like it handles the Configure() and ConfigureServices() methods in the Startup class differently. I'm trying to dig through the differences now, but was hoping you might be able to provide some insight.

    Thanks!

    Ryan

  • My query was about free and i have found my answer. Get SSL certification in one hour. https://www.fiverr.com/share/0z7zr

  • do i have to create the .wellknown folder and how do i generate the {longid} do i just make one up?

  • great idea

  • Thanks for sharing this information with us.
    The web app is an application program that is stored on a remote server and delivered over the Internet through a browser interface. Web services are Web apps by definition and many, although not all, websites contain Web apps.

  • Thanks - great instructions, very helpful!
    Just a note - SSL doesn't seem active on this site :-p

  • This is so chock full of useful information I can't wait to dig deep and start utilize the resource you have given me. Your exuberance is refreshing.

  • Thanks for writing this article.

    I had some issues deploying this to a .NET Core 3 app via Azure DevOps - I believe it was to do with the "WEBSITE_RUN_FROM_PACKAGE" being set as on which essentially makes the wwwroot folder readonly, meaning the acme-challenge files can't be written and served to Let's Encrypt.

    I managed to resolve this by changing the configured webRootPath to "./site/letsencrypt" and then In App Service > Settings > Configuration > Path mappings created a virtual application "/.well-known" to "site\letsencrypt\.well-known".

  • tanx for this EDU

  • Many Thanks for your emais.

  • Aw, this was a very nice post. Taking the time and actual effort to create a good article… but what can I say… I put things off a whole lot and never manage to get anything done.

  • Thank you so much for this post -- it is a huge help! I did have some questions as I was attempting this though. According to the webapp-renewer documentation, the letsencrypt:webAppName-clientSecret is supposed to be a connection string. In this walkthrough, you created a client secret in the App Registration - is that what is supposed to go here? Are we filling in the actual secret or the key to the secret? (unfortunately, the script doesn't work for me as it doesn't like one of my existing connection strings)

  • I am very thankful that you are my teacher.

  • Thanks for the instructions! The link to `Set-LetsEncryptConfiguration.ps1` powershell script is broken. It looks like it's been moved to:

    https://github.com/ohadschn/letsencrypt-webapp-renewer/blob/master/src/Scripts/Set-LetsEncryptConfiguration.ps1

  • For those running into the "The term 'Login-AzureRmAccount' is not recognized as the
    name of a cmdlet" problem, here's what I discovered.

    The script here uses AzureRM, but that's been replaced by Az. You can't have them both installed at the same time. So I did the following:

    1. Install Azure Az (you might need to uninstall AzureRM first).

    if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-Module -Name AzureRM -ListAvailable)) {
    Write-Warning -Message ('Az module not installed. Having both the AzureRM and ' +
    'Az modules installed at the same time is not supported.')
    } else {
    Install-Module -Name Az -AllowClobber -Scope CurrentUser
    }

    2. Set up the compatibility aliases

    Enable-AzureRmAlias -Scope CurrentUser

    And then run the script. Hope that helps.

  • Thank you for this write up it has saved me some $.
    The only thing I'm having an issue with is generating multiple certs.

    For example I need:
    acme.com
    and
    www.acme.com

    I have tried changing the hosts configuration parameter to a comma separated list of the FQDNs but that does not seem to work.

    Any help much appreciated.

  • Update: I read the documentation at: https://github.com/sjkp/letsencrypt-siteextension

    ;)

    And I see that the config parameter letsencrypt:Hostnames should have a _semi-colon_ list of domains.

    Does anyone know which parameters to change in Set-LetsEncryptConfiguration.ps1.

    Snipped the parameter list.

    This works:
    .\Set-LetsEncryptConfiguration.ps1 -WebApp acme-com-web-site -Hosts acme.com

    This does not work:
    .\Set-LetsEncryptConfiguration.ps1 -WebApp acme-com-web-site -Hosts acme.com;www.acme.com

    The error is:
    The term 'www.acme.com' is not recognized as the name of a cmdlet...

  • good article , enjoyed reading

  • Kullanmış olduğunuz mekanların daha kullanışlı ve ferah bir hale getirilmesi için <a href="https://www.vamimarlik.com">beylikdüzü mimarlık ofisleri</a> olarak tasarım projeleri hazırlanmaktadır.

    Ofis, mağaza, kafe, iç mekan ya da showroom tasarımı konusunda dekorasyoncu farklı fikirler oluşturarak kullandığınız alanın daha etkileyici görünmesini sağlamaktadır. Yıllardır bu alanda çalışmalarını sürdürmekte olan firma 2016 yılından itibaren aralıksız şekilde çalışmalarına devam etmektedir. Elde edilen deneyimler doğrultusunda tamamen profesyonel şekilde hizmet sunulmaktadır. Bu sayede mimarlık firmaları arasında özellikle adını duyurmayı başararak kalitesi ile farkını ortaya koymaktadır. Hazırlanacak olan projeler son sistem teknolojiye sahip cihazlar üzerinden gerçekleştirilmektedir. Detaylı şekilde gerçekleştirilen çalışmalar sayesinde tamamen müşteri beklentilerini karşılayacak şekilde hizmet sunulmaktadır.

  • Thanks for the good content you wrote, I always read your blog

  • I followed all steps, but failed to create the certificate. Not sure what went wrong.

    [04/27/2021 06:00:48 > c45fb1: INFO] Authorization Result: Invalid, more info at https://acme-v02.api.letsencrypt.org/acme/chall-v3/12653933375/0cdRYQ
    [04/27/2021 06:00:48 > c45fb1: INFO] AzureLetsEncryptRenewer.exe Information: 0 : Auth Result Invalid, more info at https://acme-v02.api.letsencrypt.org/acme/chall-v3/12653933375/0cdRYQ
    [04/27/2021 06:00:48 > c45fb1: INFO] Error during Request or install certificate System.Exception: Unable to complete challenge with Lets Encrypt servers error was: {"type":"http-01","url":"https://acme-v02.api.letsencrypt.org/acme/chall-v3/12653933375/0cdRYQ","status":"Invalid","validated":"2021-04-27T06:00:42+00:00","error":{"Type":"urn:ietf:params:acme:error:unauthorized","Detail":"Invalid response from https://abaitc.org/.well-known/acme-challenge/ZfAWYDYBkO4tOCfhq28FaFmTwiZ6M_6oQZa9O3SK8wk [104.43.140.101]: \"<!doctype html><html lang=\\\"en\\\"><head><meta charset=\\\"utf-8\\\"/><link rel=\\\"icon\\\" href=\\\"/favicon.ico\\\"/><meta name=\\\"viewport\\\" content=\"","Identifier":null,"Subproblems":null,"Status":403},"errors":null,"token":"ZfAWYDYBkO4tOCfhq28FaFmTwiZ6M_6oQZa9O3SK8wk","keyAuthorization":null}

  • درآموزش تعمیرات برد های الکترونیکی به شما نحوه تعمیرات انواع بردهای لوازم خانگی، تعمیرات بردهای صنعتی، تعمیرات برد پکیج، تعمیرات برد کولر گازی، تعمیرات برد اینورتر و ... آموزش داده خواهد شد.
    https://fannipuyan.com/electronic-boards-repair-training/

  • کلینیک لیزر شفا به عنوان بهترین مرکز درمان بیماری های گوارشی و نشیمن گاهی با مدیریت دکتر داود تاج بخش در رابطه با بیماریهای ناحیه مقعد خدماتی از جمله درمان بیماری هایی مثل هموروئید، فیستول، شقاق ارائه می دهد. کلینیک لیزر شفا فقط و فقط بر روی بیماری های مقعدی تمرکز داشته و تمامی متخصصین در این رابطه دور هم گرد آورده است.
    https://laser-doc.com/

Add a Comment

As it will appear on the website

Not displayed

Your website