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.
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.
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.
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!
With the Basic plan and the CPU option "Regular Intel with SSD" for $5/mo:
For authentication, I chose the Password option instead of the SSH keys option.
The password requirements are a bit interesting:
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:
Click on it, and you'll see the Console link on the right:
Click on that and you will be logged in automatically as root:
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:
Selected the default "keep the local version currently installed."
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:
Then click the Open button and login as root with the password you assigned. You will first see:
Click Accept and complete the login process and you should see something similar to:
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:
(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!)
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:
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.
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.
We don't want .NET managing the HTTPS connection -- this will be done by Nginx, so we open WinSCP:
Click Yes on the certificate prompt:
And on the right panel, navigate to /home/testapi/Properties:
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:
Notice that the web API is now listening on port 5000 and not listening on https.
Again in WinSCP, navigate to /etc/nginx/site-available:
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
You should now be able to browse to the web API:
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.
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:
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:
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:
The certbot service runs every few hours - this does not mean it refreshes to certificate every time it runs!
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:
- Listening on port 443:
server {
# SSL configuration
#
listen 443 ssl ;
listen [::]:443 ssl ;
- The server name is configured:
server_name www.temporalagency.com temporalagency.com; # managed by Certbot
- 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
- 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
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:
Using WinSCP, create a service definition file in /etc/systemd/system:
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.
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:
- Download and install VirtualBox from Oracle
- Download the desired Ubuntu or Debian image
- If you install Debian: Install .NET on Debian - .NET | Microsoft Docs
- 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:
In this article:
- We created a Digital Ocean Droplet.
- Selected the correct version of Ubuntu for playing nice with .NET 6.
- Installed PuTTY and WinSCP on our local computer for easy access to the Droplet.
- Configured our domain registrar for the Droplet.
- On the Droplet:
- Installed .NET 6.
- Installed Nginx.
- Created a test web API.
- Configured our Droplet for SSL.
- Modified the .NET web API to not use HTTPS redirect.
- Used certbot to create a 90 certificate with LetsEncrypt.
And the end result is a functional demonstration of a valid HTTPS web API!
- 1st March, 2022: Initial version