CODINGTHOUGHTS

A blog about C#, Python, Azure and full stack development

How to Host Multiple HTTPS .NET Core Applications on a Single Cheap Linux VPS

Introduction

Cloud providers such as Microsoft Azure and AWS offer an increasingly dizzying array of offerings such as serverless, cognitive services and containerisation. This is great news in enterprise settings but for individuals or even small businesses, playing with the latest tools quickly gets expensive.

For those of us wanting to host small .NET Core / MVC/ Web APIs, applications and sites, perhaps the cheapest way to get online is to take advantage of the countless cheap Linux hosts out there.

At the end of this post, you will be able to:

  • Understand the benefits of choosing cheap .NET Core VPS hosting
  • Set up multiple .NET Core applications on a single cheap Linux VPS
  • Deliver these applications over HTTPS without the extra cost of SSL certificates
  • Secure your sites from common malware and brute force attacks
  • Secure your server with a firewall

Select Your VPS

The first step on this path is to select a hosting provider, we need full control of the server so a shared hosting package is probably not appropriate. A virtual private server (VPS) is the cheapest option available giving us 100% control at an operating system level. The market is saturated with very good value options, at the lower end of the market you can expect to pay between £3 and £5 for a low spec VPS. This will be sufficient to host a number of low traffic sites, host any databases or supporting software that is required.

If you are expecting a higher volume of traffic or are hosting applications that require more grunt, the sky is the limit in terms of higher spec packages.

Here’s an example of a good value VPS deal at time of writing:

Company: OVH

Deal: VPS SSD 1 

Price: £3.50

Setting Up Your VPS

One thing you will probably be asked to do when you have selected your VPS package is to choose which Linux distribution you wish to have installed on the server. For the purposes of this post, we are using Ubuntu 18.04, but any of the major distributions that are commonly available preinstalled on VPS’s will suffice.

Once you have signed up for your VPS, you will typically receive details on how to log into the server using SSH, I recommend using PuTTY to connect, free download here.

Hostname

One thing you will need to do before we dive into the configuration of your server is point your domain name or names to it. This can ordinarily be done through your VPS provider or domain registrars DNS settings and may vary from provider to provider. For our example we will assume that the old favourite domain www.example.com is pointing to our VPS’s IP address.

Setting up the firewall

Once you have logged into your VPS it is a good idea to install and configure a firewall. This will block any undesirable connections or exploits. It is good practice to deny access to all ports other than the ones you are definitely going to use.

The firewall we are going to use is called UFW (Uncomplicated Firewall). This comes pre-installed on Ubuntu, albeit disabled. If your Linux distribution does not already have UFW installed (you can find out by typing ‘ufw status’), you can install it using the following:

sudo apt-get install ufw

Once UFW is installed we need to defined which ports are going to be opened up. In our case we want to allow incoming http, https, ssh and sftp connections:

sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable

The final command enables UFW, it is a good idea to do this after you have configured UFW to allow incoming SSH, as you may find yourself locked out otherwise.

Congratulations, your VPS is now protected from all manner of malicious exploits that may attempt to probe and connect to open ports on your server!

Install the web server

As the VPS we are using in this example is not some highly specced beast with hundreds of gigabytes of memory and an impressive multi-core CPU, we need to select a web server that achieves a balance between resource consumption and features. We also need to select a web server that can act as a reverse proxy, this is so that we can host multiple sites, each mapped to their own domain names. In effect the reverse proxy will act as a façade that received a web request and routes it to the appropriate .NET application, this will be covered later in this post.

NGINX

A good choice based upon these requirements is NGINX, although you may have great success with other web servers such as Apache or Lighttpd.

To install NGINX use the following:

sudo apt update
sudo apt install nginx

Once this is complete you may navigate to http://www.example.com where you will be greeted with the default NGINX welcome page.

This page is actually located in the default location on disk at /var/www/html

ls /var/www/html

index.nginx-debian.html

If you want to simply host a simple static web site on your VPS, you could quite happily place all of your content here and go no further. We, however, have more ambitious plans and aim to host multiple .NET Core applications and web services on our humble little VPS!

Install .NET Core

Before we return to completing our NGINX configuration, now is a good time to install our web application. Once this is done we will have a .NET Core MVC web application exposed on port 5001. As and when external web requests are received by NGINX for www.example.com, they will be routed through to port 5001 and the result returned back to the end user, this is what we refer to as reverse proxying. The beauty of this configuration is that we may have several separate web application or services running on portal 5001,5002,5003 etc. each mapped to a different domain name by NGINX.

So the first step to getting our .NET application up and running is to install .NET Core 2.2. There is a bit of prep that we need to perform before installing the actually .NET Core framework, this is to do with registering Microsoft’s key and product repository:

wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Selecting previously unselected package packages-microsoft-prod.
(Reading database ... 61188 files and directories currently installed.)
Preparing to unpack packages-microsoft-prod.deb ...
Unpacking packages-microsoft-prod (1.0-ubuntu18.04.2) ...
Setting up packages-microsoft-prod (1.0-ubuntu18.04.2) ...

Once this is done, it is time to install .NET Core 2.2:

sudo add-apt-repository universe
sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install dotnet-sdk-2.2

'universe' distribution component is already enabled for all sources.


Reading package lists... Done
.....
Setting up apt-transport-https (1.6.11) ...
.....
Fetched 3,499 kB in 3s (1,315 kB/s)
Reading package lists... Done
[hit 'Y' when prompted]

After this you can confirm .NET Core is installed by issuing the ‘dotnet –version’ command. At time of writing, this returns 2.2.300.

Install Your Application

Once we are happy with this it is time to install our application. A common way to do this would be to publish locally from your development environment and copy the artefacts to our VPS. So for example, if we have an MVC application, we would use Visual Studio’s publish functionality to generate artefacts that could be copied over to your VPS.

Alternatively, we can create a simple HelloWorld MVC web application using dotnet’s command line interface. Create a new directory and cd into it, then issue the following command:

dotnet new mvc

The template "ASP.NET Core Web App (Model-View-Controller)" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore-template-3pn-210 for details.

Processing post-creation actions...
Running 'dotnet restore' on /root/HelloWorld/HelloWorld.csproj...
  Restore completed in 1.99 sec for /root/HelloWorld/HelloWorld.csproj.

Restore succeeded.

We now have our, albeit simple, application. Use the ‘dotnet run’ command to fire up the application. This will create a standalone server running on port 5000:

# dotnet run
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using '/root/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
      Creating key {44ef3e6a-7e65-4782-aac5-46e0abd1ad01} with creation date 2019-06-25 17:17:07Z, activation date 2019-06-25 17:17:07Z, and expiration date 2019-09-23 17:17:07Z.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {44ef3e6a-7e65-4782-aac5-46e0abd1ad01} may be persisted to storage in unencrypted form.
info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
      Writing data to file '/root/.aspnet/DataProtection-Keys/key-44ef3e6a-7e65-4782-aac5-46e0abd1ad01.xml'.
Hosting environment: Development
Content root path: /root/HelloWorld
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

To demonstrate the fact that we can host multiple sites on a single NGINX instance, we are going to map an additional domain name to our new application. In our example we will use www.example2.com, obviously you will need to replace this with your own domain name.

So, we want to route any requests from our https://www.example2.com URL through to the dotnet application running on port 5000. To do this we simply add a new configuration file to /etc/nginx/sites-available:

sudo nano /etc/nginx/sites-available/www.example2.com

Add the following contents and save using CTRL-o CTRL-x

server {
    listen        443;
    server_name   example2.com *.example2.com;
    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

And then create a link and restart NGINX using:

sudo ln -s /etc/nginx/sites-available/example2.com /etc/nginx/sites-enabled/
sudo systemctl restart nginx

Previously we created a new SSL certificate using certbot for www.example.com. As we are using a second domain, we need to do the same for this one using:

sudo certbot --nginx -d www.example2.com

You should see something like the following when this command is executed. In this example we will select option 2 to redirect all HTTP web requests to HTTPS:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Obtaining a new certificate
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/example2.com

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/example2.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://www.example2.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=www.example2.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/www.example2.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/www.example2.com/privkey.pem
   Your cert will expire on 2019-11-21. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew *all* of
   your certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

And once this is done you should restart NGINX using:

sudo systemctl restart nginx

and then navigate to https://www.example2.com. If all has gone well, you should now see your application load up over HTTPS. Any requests to http://www.example2.com should be redirected to the HTTPS version.

Conclusion

This whistle-stop demonstration has shown, in principal, how to set up multiple web sites/applications on a single low cost Linux server. There are many additional concerns that you should look into such as security and more advanced NGINX configuration, but this bare-bones setup should suffice for whatever personal or small scale web projects you want to host.

Think about the cost of multiple standard hosting accounts compared to the £3 or so that this solution costs. For the price of a little configuration some significant year on year savings can be made!


Posted

in

by

Tags:

Comments

Leave a Reply