Hosting a .NET Core 3.1 MVC web on Amazon Elastic Compute Cloud (EC2)

I undertook an adventure starting this past weekend to get some experience with Amazon Web Services (AWS).   I started by taking several beginner courses on Pluralsight.  This course, AWS Developer: Getting Started by Ryan Lewis, is wonderfully detailed and walks you through setting up an EC2 instance, adding security groups, granting access to ports and other important base concepts.   But he was using a NodeJs app as an example app to host on AWS.   I wanted to learn how to host a .NET Core app instead.

I built a little example app which was helping me to study for an Amazon interview that served up random interview questions or one of Amazon’s Leadership Principles.  Great.  Now how do you go about hosting a .NET Core app on EC2?

The EC2 instance I chose was the Amazon Linux 2 operating system.   Note that this closely (read “is the same as”) mirrors the Centos 7 flavor of Linux which supports .NET Core.   I found some invaluable “how to” videos on YouTube by Jon Jensen.  They were a little dated since they came from 2018 and targeted an older version of .NET Core, Amazon Linux, etc.

The first thing you do after updating the Amazon Linux 2 OS with the latest patches was to install .NET Core on the EC2 instance.   This document on Microsoft’s site was excellent and I didn’t have any issues.

Next up was packaging the .NET code.  You compile a .NET Core app with the following command line:

$ dotnet publish -o awssite

This creates a directory called “awssite” (you can name it anything) and packages all the dependencies.   How do you get your code to the EC2 instance?  I’m sure there are several ways, but I chose an SSH utility.  What is wonderful is that Microsoft has now included SSH tools in Windows as a native optional feature.   Now you can follow along with tutorials that use Unix style command line examples.  This command copied my compiled output to a folder on the instance:

C:\dox\code\git\AmazonInterviewQuiz\awssite>scp -r -i c:\dox\code\AWS\[YOUR_SSH_KEY_FILE].pem c:\dox\code\git\amazoninterviewquiz\awssite\ ec2-user@[YOUR_EC2_EXTERNAL_IP]:/home/ec2-user/[DIRECTORY_FOR_COMPILED_CODE]

Great so far!

After your code is staged on the EC2 instance, you have to have some way to map inbound HTTP requests to your .NET Core app.   .NET Core apps use Kestrel self-hosting and you must configure a reverse proxy on Linux to forward the request to Kestrel. Again, Microsoft has a very nice document for how to do this.

Here’s where we have a Google/StackOverflow moment.   I had to make changes to the configuration file.  My “awssite.conf” file is as follows after some trial and error (namely the log file and removal of a superfluous entry):

[ec2-user@ip-10-0-1-72 conf.d]$ more awssite.conf

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ErrorLog /var/log/httpd/awssite-error.log
    CustomLog /var/log/httpd/awssite-access.log common
</VirtualHost>

You can see the differences if you look at the example in the Microsoft web page for Apache install.

Okay, now for an ad hoc test of the app!  You start a .NET Core app with the following simple syntax (again all of this is via SSH):

[ec2-user@ip-10-0-1-72 awssite]$ dotnet AmazonInterviewQuiz.dll
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/ec2-user/timeline/awssite

After testing the config and bouncing the Apache process, I then tried to issue a curl request to test the localhost functionality.   I got zero bytes.   Looking at the output closely, I noticed TWO ports were listening?  Why was https listening on 5001?  Anyways, I got an empty curl response which wasn’t good but didn’t tell me anything more.

I opened the awssite-error.log and the awssite-access.log and found nothing suspicious. I fired up a browser on my local Windows PC and received an HTTP 502 error.  Hmmm.  I fired up trusty Fiddler and discovered the initial request was immediately redirected from the 5000 port to the 5001 port.  What is going on?

After some StackOverflow deep dives, here’s the “problem”.  Microsoft introduced a “feature” in the recent releases of .NET Core to try to redirect always from http to https if the initial request was http.   I was ignorant of that.   This post on StackOverflow and this other one gave me the direction I needed to take.

I made two changes to my source code.  First, I removed this line from the boilerplate scaffolding code in Startup.cs:

app.UseHttpsRedirection();

Secondly, I manually configured the webserver in Program.cs:

public static IHostBuilder CreateHostBuilder(string[] args) =>
     Host.CreateDefaultBuilder(args)
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder.UseKestrel(options =>
             {
                 options.Listen(IPAddress.Loopback, 5000);
             });
             webBuilder.UseIIS();
             webBuilder.UseStartup<Startup>();
         });

Here, you see I have OMITTED the default of 5001 which effectively disables https support for the app.   This is OF COURSE not recommended for production apps.

After that was tested locally I had to republish the code to the EC2 instance and restart my app.   Everything now works perfectly.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s