In this post I'll show you how to use Let's Encrypt PEM certificates to provide SSL/TLS for your ASP.NET 5 Application.

Why would you need this?

If you're running your ASP.NET application in Production mode you need a valid certificate. Self-signed certificates or those generated with dotnet dev-certs wont cut it when visitors start using your application.

When you run your application and Kestrel can't find a certificate you'll get an error something like this:

$ dotnet ExampleCom.dll
[11:11:52 INF] Starting up
[11:11:52 FTL] Unable to start Kestrel.
Interop+Crypto+OpenSslCryptographicException: error:2006D080:BIO routines:BIO_new_file:no such file
   at Interop.Crypto.CheckValidOpenSslHandle(SafeHandle handle)
   at Internal.Cryptography.Pal.OpenSslX509Encoder.GetCertContentType(String fileName)
   at System.Security.Cryptography.X509Certificates.X509Certificate2.GetCertContentType(String fileName)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates.CertificateConfigLoader.GetCertificate(String certificatePath)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates.CertificateConfigLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Reload()
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
[11:11:52 FTL] Application start-up failed
Interop+Crypto+OpenSslCryptographicException: error:2006D080:BIO routines:BIO_new_file:no such file
   at Interop.Crypto.CheckValidOpenSslHandle(SafeHandle handle)
   at Internal.Cryptography.Pal.OpenSslX509Encoder.GetCertContentType(String fileName)
   at System.Security.Cryptography.X509Certificates.X509Certificate2.GetCertContentType(String fileName)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates.CertificateConfigLoader.GetCertificate(String certificatePath)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates.CertificateConfigLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Reload()
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at ExampleCom.Program.Main(String[] args) in /home/tony/exampleCom/src/ExampleCom/Program.cs:line 25

This is Kestrel's way of telling you it needs a certificate.

Kestrel Needs a Certificate

What's Special About Let's Encrypt Certificates?

Let's Encrypt certificates are free, and after you set them up they'll be renewed automatically. This makes providing SSL/TLS for your application very easy.

The problem that many .NET developers have faced is that until .NET 5 there wasn't an easy way to directly use the PEM certificates Let's Encrypt produces with Kestrel as it only supported PFX certificates. There are a series of step you can take to generate a PFX certificate from a PEM certificate using OpenSSL, but that's beyond the scope of this article.

Likewise I wont get into the difference between the PEM and PFX certificate types here either. Just know that Let's Encrypt generates PEM certificates and as of around July 2020 PEM certificates can be used for SSL/TLS encryption in with Kestrel!

The Sample ASP.NET 5 Application

Let's get into an example of how this could work. For this sample I'll be working with an ASP.NET 5 application inside of a Docker container, which will be running behind an Nginx reverse proxy on an Ubuntu 20.04 server. That's a lot of information, so here's a diagram to illustrate what the sample application environment looks like:

Sample Setup Diagram

Assumptions

I'm also going to make a few assumptions:

  1. You've already got an Ubuntu server set up (you can use another distro).
  2. Your DNS is updated for your domain to point at your server.
  3. You are getting certificates using Let's Encrypt/Certbot.
  4. You have a .NET 5 based ASP.NET application (site/api)

If any of these are untrue, it's probably a good idea to stop and get to this point before continuing.

Using Let's Encrypt PEM Certificates With ASP.NET 5

Telling your ASP.NET 5 application to use the Let's Encrypt PEM certificates is pretty straightforward. Configure your appsettings.production.json like this:

{
  "UrlDomain": "www.example.com",
  "Kestrel": {
    "EndPoints": {
      "Http": {
        "Url": "http://0.0.0.0:5000"
      },
      "HttpsFromPem": {
        "Url": "https://0.0.0.0:5001",
        "Certificate": {
          "Path": "/etc/letsencrypt/live/example.com/cert.pem",
          "KeyPath": "/etc/letsencrypt/live/example.com/privkey.pem"
        }
      }
    }
  }
}

Here we're sharing the certificates with that Let's Encrypt creates (cert.pem & privkey.pem) with the Kestrel web server. You may need to modify the permissions on your certificates. This is fine but be sure to refer to the Let's Encrypt docs to make sure you understand what you're doing first.

Now when you use the command dotnet example.dll (or dotnet run with the SDK) your application should start up instead of displaying the "Unable to start Kestrel." errors.

Setup Dockerized Kestrel to Use Let's Encrypt Certificates

Setting up an ASP.NET 5 application in a Docker container to use your PEM certificates takes a little more typing. The basic idea in this scenario is that we want to "pass in" the PEM certificates to the Docker container as a volume.

If you're familar with the way Let's Encrypt stores certificates you'll know there are essentially two directories, a bunch of PEM certs, and there are also symbolic links to the actual certificates. Basically the directory structure looks something like this:

/etc/letsencrypt
    /archive/example.com - contains the actual PEM certificates are stored
    /live/example.com - contains symbolic links to the current PEM certificate

In order for Docker to be able to access the certificates, we need to set up a volume for both the "archive" and "live" directories. I prefer to do this as two volumes so that we don't expose unrelated certificates to the Docker container.

Starting Up Our Docker Container

Start your Docker container specifying the volumes for the Let's Encrypt certificates like this:

docker run -it
  -e ASPNETCORE_ENVIRONMENT=production
  -v /etc/letsencrypt/live/example.com:/https/live/example.com
  -v /etc/letsencrypt/archive/example.com:/https/archive/example.com
  -p 5001:5001
  example-service-image

Notice that the volume is being mounted at the /https/... path inside the container. You may also recall that we had set the appsettings.production.json file to expect the PEM certificates to be located in the /etc/letsencrypt/live/example.com/ path. We'll need to change that.

Update your appsettings.production.json to match the container's mount points:

{
    ...
      "HttpsFromPem": {
        "Url": "https://0.0.0.0:5001",
        "Certificate": {
          "Path": "/https/live/example.com/cert.pem",
          "KeyPath": "/https/live/example.com/privkey.pem"
          }
      }
    ...
}

Now the ASP.NET 5 application will expect the PEM certificates to be located in the /https/... path. Make sure to rebuild your ASP.NET code and your Docker image with the change to the appsettings.production.json file, and start it up using the earlier docker run command. When your application starts you should now be error free!

That's it! I hope you enjoy the benefits of free, auto-renewing PEM certificates from Let's Encrypt with your future ASP.NET applications.