Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Debian

Create a Digital Ocean Droplet for .NET Core Web API with a real SSL Certificate on a Domain

5.00/5 (13 votes)
1 Mar 2022CPOL9 min read 29.8K  
I want to host some simple applications under a real HTTPS domain. This article describes my foray into doing exactly this with a Digital Ocean Droplet.
I want to host some simple .NET Core Web API applications under a real HTTPS domain. This article describes my foray into doing exactly this with a Digital Ocean Droplet. Except for the creation of the VM, the steps described here apply to other VM providers such as Azure or AWS as well. The SSL certificate is generated using LetsEncrypt and the certbot utility.

Introduction

I want to host some simple applications under a real HTTPS domain. This article describes my foray into doing exactly this with a Digital Ocean Droplet. It's actually quite easy but there were some interesting curve balls.

In the screenshots, I am actually walking through the Droplet creating / configuration process of a real droplet as I write this. After writing this article, the Droplet will be deleted so any sort-of-secure information that I may present here is irrelevant.

Except for the creation of the VM, the steps described here apply to other VM providers such as Azure or AWS as well.

The Steps

Step 0: Install WinSCP and PuTTY on Your Computer

It's a lot more convenient to work with a remote server using WinSCP and PuTTY, so install these apps on your local machine.

  • Download and install PuTTY from here.
  • Download and install WinSCP from here.

Step 1: Create a Droplet

Sign up with Digital Ocean and create a Droplet.

IMPORTANT: Make sure you create an Ubuntu 18.04 (LTS) x64 droplet! Ubuntu 20.04 is not a happy camper with .NET!

Image 1

With the Basic plan and the CPU option "Regular Intel with SSD" for $5/mo:

Image 2

For authentication, I chose the Password option instead of the SSH keys option.

Image 3

The password requirements are a bit interesting:

Image 4

Step 2: Install openssh-server

I don't want to use Digital Ocean's console as PuTTY is a bit more sophisticated (for example, copy and paste is really easy.) That said, we have to start with the console.

After the Droplet has been created, you'll see it in the Resources tab:

Image 5

Click on it, and you'll see the Console link on the right:

Image 6

Click on that and you will be logged in automatically as root:

Image 7

First, make sure the version of Ubuntu is up to date by running:

sudo apt update

As Ubuntu 18 does not have openssh installed, install it with:

sudo apt install openssh-server

You will see this prompt:

Image 8

Selected the default "keep the local version currently installed."

Step 3: Connect with PuTTY

Image 9

Note the Droplet's IP address. Copy the IP -- a convenient Copy button appears when you hover over the IP. Open Putty and enter the IP:

Image 10

Then click the Open button and login as root with the password you assigned. You will first see:

Image 11

Click Accept and complete the login process and you should see something similar to:

Image 12

Step 4: Install .NET 6

Run these commands in the PuTTY terminal window as described here: Install .NET on Linux with Snap - .NET | Microsoft Docs.

The simplest way to do this is to copy the each line in the code block below, left-click on the terminal window, and right-click to paste.

sudo snap install dotnet-sdk --classic --channel=6.0
sudo snap alias dotnet-sdk.dotnet dotnet 
sudo snap install dotnet-runtime-60 --classic
export DOTNET_ROOT=/snap/dotnet-sdk/current

Note that for a Debian install, the commands are different, as described here: Install .NET on Debian - .NET | Microsoft Docs.

IMPORTANT! I tried the above commands with an Ubuntu VirtualBox using the latest version of Ubuntu and while .NET installed, trying to run the sample Weather Forecast .NET application resulted in this error: GLIBC_2.2.5 not defined in file libpthread.so.0 with link time reference so ironically, I ended up using a Debian VirtualBox image as this was the only solution people said "worked." That said,

Check the .NET 6 install with:

sudo dotnet --info

and you should see:

Image 13

(The astute reader will notice that my Droplet name changed - I had originally installed Ubuntu 20.04 and then discovered it doesn't work with .NET 6!)

Step 5: Create the Example WeatherForecast Web API

Again in the PuTTY console, copy and paste:

From the /home folder, create testapi then:

cd /home
mkdir testapi
cd testapi
dotnet new webapi
dotnet build
dotnet run

You should see:

Image 14

If you don't - if dotnet run simply returns to the command line, this means you have created the Droplet with the wrong version of Ubuntu!

Stop the web API by pressing Ctrl+C. We need to make some configuration changes later on.

Step 6: Install Nginx

I'm using Nginx because of its reverse proxy support, which we will see is necessary to route incoming requests to the .NET Web API application.

Install Nginx with:

sudo apt-get install nginx

and start the server with:

sudo /etc/init.d/nginx start

We'll be changing the configuration of Nginx, so this command:

sudo /etc/init.d/nginx reload

to reload Nginx, is useful to know.

Step 7: Configure the .NET Launch Settings

We don't want .NET managing the HTTPS connection -- this will be done by Nginx, so we open WinSCP:

Image 15

Click Yes on the certificate prompt:

Image 16

And on the right panel, navigate to /home/testapi/Properties:

Image 17

Double-click on launchSettings.json and:

On this line:

"applicationUrl": "https://localhost:7176;http://localhost:5129",

Remove the "https" route and change the localhost to 5000 so it reads:

"applicationUrl": "http://localhost:5000",

Save the file -- the "disk" icon in the upper right of the editor window.

Restart the web API with:

dotnet build
dotnet run

and you should see:

Image 18

Notice that the web API is now listening on port 5000 and not listening on https.

Step 8: Configure Nginx

Again in WinSCP, navigate to /etc/nginx/site-available:

Image 19

And double-click on "default".

Edit the "location" configuration so it reads:

location / {
    # First attempt to serve request as file, then
    # as directory, then fall back to displaying a 404.
    # try_files $uri $uri/ =404;
    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;
}

The change we made here is to comment out the "try_files" option and most importantly, to add the proxy_pass line:

proxy_pass http://localhost:5000;

along with some proxy setting options.

IMPORTANT: We must use localhost, not 127.0.0.1, otherwise the web API will not respond.

Reload Nginx with:

sudo /etc/init.d/nginx reload

Step 9: Test the Web API

You should now be able to browse to the web API:

Image 20

Step 10: The SSL Certificate with Certbot and LetsEncrypt

There's a good writeup here: How to Set Up an Nginx Certbot (haydenjames.io)

At this point, you must have a domain registered. Edit the DNS records on your domain, creating two A records. I use Namecheap, so my A records look like this for my domain temporalagency.com. The IP in your A records should be the IP address of your Droplet.

Image 21

You'll have to wait 24 hours or so for this to percolate through the Interwebs before you can install the SSL certificate.

In the meantime, you can install Certbot:

sudo apt install python-certbot-nginx

The firewall should also be configured with the commands:

ufw allow ssh
ufw enable
ufw allow http
ufw allow https
sudo ufw allow 'Nginx Full'
sudo ufw status

The last command displays the firewall status which, after running the above commands, should look like this:

Image 22

Once you've given the A records time to update through the Inernet's DNS system, you can run this command:

sudo certbot --nginx -d [domain].[ext] -d www.[domain].[ext]

where [domain] is your domain name and [ext] is the generic top-level name such as com, org, net, and so forth. You will be prompted for an email address for alerts as well as some other opt-in questions, including whether http should be automatically rerouted to https, which I answer yes to.

At this point, you should be able to access the web API with https://www.[domain].[ext]/WeatherService, for example, using my domain temporalagency.com:

Image 23

To manually renew the certificate (it expires every 90 days), you can use this command:

sudo certbot renew --dry-run

However, the certificate should auto-renew. You can verify that it is in a systemd timer with the command:

systemctl list-timers

and you should see:

Image 24

The certbot service runs every few hours - this does not mean it refreshes to certificate every time it runs!

What did certbot do?

If you look at the default file in the Nginx folder, you will see that certbot created a new server definition file, the salient points are:

  1. Listening on port 443:
    server {
    
    	# SSL configuration
    	#
    	listen 443 ssl ;
    	listen [::]:443 ssl ;
  2. The server name is configured:
    server_name www.temporalagency.com temporalagency.com; # managed by Certbot
  3. The certificate is set:
    ssl_certificate /etc/letsencrypt/live/temporalagency.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/temporalagency.com/privkey.pem; # managed by Certbot
  4. A redirect (assuming you asked certbot to redirect http to https) is configured:
    server {
        if ($host = www.temporalagency.com) {
            return 301 https://$host$request_uri;
        } # managed by Certbot
    
    
        if ($host = temporalagency.com) {
            return 301 https://$host$request_uri;
        } # managed by Certbot
    
    	listen 80 ;
    	listen [::]:80 ;
        server_name www.temporalagency.com temporalagency.com;
        return 404; # managed by Certbot

Step 11: Publish and Keep Your Web API Running

Lastly, we want to keep the web API running after we close the PuTTY window or restart automatically if the server is restarted.

First, comment out the line:

// app.UseHttpsRedirection();

as Nginx is handling the https redirection.

Next, create a release version of your application:

dotnet publish --configuration Release

This creates a Publish folder which we can see in WinSCP:

Image 25

Using WinSCP, create a service definition file in /etc/systemd/system:

Image 26

For example, I simply called mine "testapi.service."

The contents of the file (from here) should read:

[Unit]
Description=Example .NET Web API App running on Ubuntu

[Service]
WorkingDirectory=/var/www/PUBLISH_DIRECTORY
ExecStart=/usr/bin/dotnet /var/www/MYAPP_DIRECTORY/STARUP_PROJECT_NAME.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Except that the /var/www/PUBLISH_DIRECTORY and /var/www/MYAPP_DIRECTORY/STARUP_PROJECT_NAME.dll need to be replaced accordingly, and as my service is in home/testapi, I used:

/home/testapi/bin/Release/net6.0/publish/ and /home/testapi/bin/Release/net6.0/publish/testapi.dll respectively:

WorkingDirectory=/home/testapi/bin/Release/net6.0/publish
ExecStart=/snap/dotnet-sdk/current/dotnet /home/testapi/bin/Release/net6.0/publish/testapi.dll

IMPORTANT: Notice the path for dotnet - an absolute path is required by the service.

Save the file and enable the service with (obviously replacing the service name with whatever service name you've chosen):

sudo systemctl enable testapi.service

Start the service with:

sudo systemctl start testapi.service

If you need to stop the service, use:

sudo systemctl stop testapi.service

Test that the service comes up after a reboot using:

sudo reboot

Wait a minute or so for the Droplet to restart, then test that the web API is working.

Step 12: Optional - Set Up a VirtualBox

I'm not particularly keen on testing code on the Droplet, and while I can obviously develop a .NET 6 application in Windows, I think it's useful to test the deployment to the Linux system using a VirtualBox image of either Ubuntu 18.04 or the current LTS release of Debian. In brief:

  1. Download and install VirtualBox from Oracle
  2. Download the desired Ubuntu or Debian image
  3. If you install Debian: Install .NET on Debian - .NET | Microsoft Docs
  4. If you install Ubuntu: Install .NET on Linux with Snap - .NET | Microsoft Docs

If you install Debian, you'll need to edit /etc/network/interfaces file on the Debian server to look similar to this, depending on your IP address and name server:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth0 
iface eth0 inet static 
address 192.168.10.115 
netmask 255.255.255.0 
gateway 192.168.10.1 
dns-nameservers 8.8.8.8 192.168.10.1

Also note that the Debian image already has openssh installed.

To use the VirtualBox image with PuTTY and WinSCP from your local computer, you'll need to create a network bridge as Adapter 2, similar to this in the Settings:

Image 27

Conclusion

In this article:

  1. We created a Digital Ocean Droplet.
  2. Selected the correct version of Ubuntu for playing nice with .NET 6.
  3. Installed PuTTY and WinSCP on our local computer for easy access to the Droplet.
  4. Configured our domain registrar for the Droplet.
  5. On the Droplet:
    1. Installed .NET 6.
    2. Installed Nginx.
    3. Created a test web API.
    4. Configured our Droplet for SSL.
    5. Modified the .NET web API to not use HTTPS redirect.
    6. Used certbot to create a 90 certificate with LetsEncrypt.

And the end result is a functional demonstration of a valid HTTPS web API!

History

  • 1st March, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)