How To Setup DotNet WebApp with Nginx and Lets Encrypt on Ubuntu

Running dotnet 7 webapp on ubuntu linux server with nginx and lets encrypt

Prerequisites

Before we get started, we need to make sure Nginx web server is set up. Use the below link to set up Nginx server on Ubuntu.

NOTE: DNS setup is beyond the scope of the article. Please ensure domain name is pointing to correct server before proceeding with the below guide.

Step 1: Install .Net SDK 7.0 On Ubuntu 22.04

sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-7.0

Output:

rahil@ubuntu:~$ sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-7.0
[sudo] password for rahil:
Hit:1 http://mirrors.digitalocean.com/ubuntu jammy InRelease
Get:2 http://mirrors.digitalocean.com/ubuntu jammy-updates InRelease [119 kB]
Hit:3 https://repos-droplet.digitalocean.com/apt/droplet-agent main InRelease
Get:4 http://mirrors.digitalocean.com/ubuntu jammy-backports InRelease [109 kB]
Get:5 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Get:6 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [722 kB]
Get:7 http://security.ubuntu.com/ubuntu jammy-security/main Translation-en [160 kB]
Get:8 http://security.ubuntu.com/ubuntu jammy-security/restricted amd64 Packages [767 kB]
Get:9 http://security.ubuntu.com/ubuntu jammy-security/restricted Translation-en [123 kB]
Fetched 2109 kB in 6s (379 kB/s)
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  aspnetcore-runtime-7.0 aspnetcore-targeting-pack-7.0 dotnet-apphost-pack-7.0 dotnet-host-7.0 dotnet-hostfxr-7.0
  dotnet-runtime-7.0 dotnet-targeting-pack-7.0 dotnet-templates-7.0 liblttng-ust-common1 liblttng-ust-ctl5 liblttng-ust1
  netstandard-targeting-pack-2.1-7.0
The following NEW packages will be installed:
  aspnetcore-runtime-7.0 aspnetcore-targeting-pack-7.0 dotnet-apphost-pack-7.0 dotnet-host-7.0 dotnet-hostfxr-7.0
  dotnet-runtime-7.0 dotnet-sdk-7.0 dotnet-targeting-pack-7.0 dotnet-templates-7.0 liblttng-ust-common1 liblttng-ust-ctl5
  liblttng-ust1 netstandard-targeting-pack-2.1-7.0
0 upgraded, 13 newly installed, 0 to remove and 61 not upgraded.
Need to get 136 MB of archives.
...

Step 2: Install .Net Runtime

sudo apt-get update && \
  sudo apt-get install -y aspnetcore-runtime-7.0

Output:

rahil@ubuntu:~$ sudo apt-get update && \
  sudo apt-get install -y aspnetcore-runtime-7.0
Hit:1 http://mirrors.digitalocean.com/ubuntu jammy InRelease
Hit:2 https://repos-droplet.digitalocean.com/apt/droplet-agent main InRelease
Hit:3 http://mirrors.digitalocean.com/ubuntu jammy-updates InRelease
Hit:4 http://mirrors.digitalocean.com/ubuntu jammy-backports InRelease
Hit:5 http://security.ubuntu.com/ubuntu jammy-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
aspnetcore-runtime-7.0 is already the newest version (7.0.110-0ubuntu1~22.04.1).
aspnetcore-runtime-7.0 set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 61 not upgraded.

As an alternative to the ASP.NET Core Runtime, you also can install the .NET Runtime. The package doesn’t include ASP.NET Core support. You can replace aspnetcore-runtime-7.0 in the previous command with dotnet-runtime-7.0.

sudo apt-get install -y dotnet-runtime-7.0

In my case, I will stick with option one as I am using ASP.NET Core in my WebApp.

Step 3: Download ASP.NET WebApp from GitHub

In this tutorial, I am using a sample app I created here. You can setup CICD pipeline to build and deploy the package directly on Ubuntu web server if you prefer. For the purpose of this tutorial, I will follow manual download, unzip, and staging of package to hosting path.

wget https://github.com/tekspace-io/DotNetWebApp/archive/refs/heads/master.zip

Output:

rahil@ubuntu:~$ wget https://github.com/tekspace-io/DotNetWebApp/archive/refs/heads/master.zip
--2023-08-31 16:24:22--  https://github.com/tekspace-io/DotNetWebApp/archive/refs/heads/master.zip
Resolving github.com (github.com)... 192.30.255.112
Connecting to github.com (github.com)|192.30.255.112|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/tekspace-io/DotNetWebApp/zip/refs/heads/master [following]
--2023-08-31 16:24:22--  https://codeload.github.com/tekspace-io/DotNetWebApp/zip/refs/heads/master
Resolving codeload.github.com (codeload.github.com)... 192.30.255.121
Connecting to codeload.github.com (codeload.github.com)|192.30.255.121|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/zip]
Saving to: ‘master.zip’

Extract zip file that was downloaded on your Ubuntu server.

Install unzip, if the command doesn’t exist.

sudo apt-get install unzip

We will execute unzip command to extract master.zip file that we downloaded from GitHub repository.

unzip master.zip -d webapp

Output:

rahil@ubuntu:~$ unzip master.zip -d webapp
Archive:  master.zip
bd1ef2df340c39be5bebd55dd2f5938dd3679677
   creating: webapp/DotNetWebApp-master/
  inflating: webapp/DotNetWebApp-master/.gitattributes
  inflating: webapp/DotNetWebApp-master/.gitignore
  inflating: webapp/DotNetWebApp-master/DotNetWebApp.sln
   creating: webapp/DotNetWebApp-master/DotNetWebApp/
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/DotNetWebApp.csproj
   creating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Error.cshtml
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Error.cshtml.cs
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Index.cshtml
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Index.cshtml.cs
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Privacy.cshtml
  inflating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Privacy.cshtml.cs
   creating: webapp/DotNetWebApp-master/DotNetWebApp/Pages/Shared/
...

Now navigate to the solution path ~/webapp/DotNetWebApp-master

cd ~/webapp/DotNetWebApp-master

Step 4: Publish DotNet code

Run dotnet publish to compile the code and publish.

dotnet publish --configuration Release

Output:

rahil@ubuntu:~/webapp/DotNetWebApp-master$ dotnet publish --configuration Release
MSBuild version 17.4.8+6918b863a for .NET
  Determining projects to restore...
  Restored /home/rahil/webapp/DotNetWebApp-master/DotNetWebApp/DotNetWebApp.csproj (in 102 ms).
  DotNetWebApp -> /home/rahil/webapp/DotNetWebApp-master/DotNetWebApp/bin/Release/net7.0/DotNetWebApp.dll
  DotNetWebApp -> /home/rahil/webapp/DotNetWebApp-master/DotNetWebApp/bin/Release/net7.0/publish/

Upon publish, you will notice the runtime code was created under ~/webapp/DotNetWebApp-master/DotNetWebApp/bin/Release/net7.0. We will copy the files from the published location to /var/www/demo_tekspace_io/.

NOTE: I am using /var/www/demo_tekspace_io/ path based on Nginx & let’s encrypt tutorial. If you would like to update demo_tekspace_io to your preferred folder name, you can do so as per your requirements.

Step 5: Stage published code to Nginx hosting path

Copy net.7.0 folder from ~/webapp/DotNetWebApp-master/DotNetWebApp/bin/Release to /var/www/demo_tekspace_io/.

sudo cp -r ~/webapp/DotNetWebApp-master/DotNetWebApp/bin/Release/net7.0/ /var/www/demo_tekspace_io/

To confirm if net7.0 was copied to the hosting path, execute ls -l /var/www/demo_tekspace_io/:

rahil@ubuntu:~$ ls -l /var/www/demo_tekspace_io/
total 8
drwxr-xr-x 2 rahil rahil 4096 Aug 30 21:20 html
drwxr-xr-x 3 root  root  4096 Aug 31 18:13 net7.0

Run the below command to change ownership of net7.0 from root to logged in sudo user.

sudo chown -R $USER:$USER /var/www/demo_tekspace_io/net7.0

You only need to do this if the folder is owned by root user.

Step 6: Configure Nginx to host DotNet 7.0

If you have followed along our previous tutorial to set up Nginx server and Let’s Encrypt guide. Below is the server block setup by Nginx and CertBot. We will modify this file and add dotnet-related configuration to host the WebApp we are referencing for this tutorial.

rahil@ubuntu:~$ cat /etc/nginx/sites-available/demo_tekspace_io
server {

        root /var/www/demo_tekspace_io/html;
        index index.html index.htm index.nginx-debian.html;

        server_name demo.tekspace.io www.demo.tekspace.io;

        location / {
                try_files $uri $uri/ =404;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/demo.tekspace.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/demo.tekspace.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = demo.tekspace.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name demo.tekspace.io www.demo.tekspace.io;
    return 404; # managed by Certbot


}

Next, we will add a reverse proxy configuration to tell Nginx web server to serve all the traffic that is served on port 80 or 443 to localhost with port 5000.

Edit file with nano editor:

sudo nano /etc/nginx/sites-available/demo_tekspace_io

Add the following content inside location / {

proxy_pass         http://127.0.0.1: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;

Next, replace root /var/www/demo_tekspace_io/html; with root /var/www/demo_tekspace_io/net7.0;

Save the file. Your server block content should look like this:

server {

        root /var/www/demo_tekspace_io/net7.0/publish/wwwroot;
        index index.html index.htm index.nginx-debian.html;

        server_name demo.tekspace.io www.demo.tekspace.io;

        location / {
                try_files $uri $uri/ =404;
                proxy_pass         http://127.0.0.1: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;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/demo.tekspace.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/demo.tekspace.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = demo.tekspace.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name demo.tekspace.io www.demo.tekspace.io;
    return 404; # managed by Certbot


}

Now, we will test Nginx configuration by executing the below command:

sudo nginx -t

If you see below output, that means your configurations are set correctly.

rahil@ubuntu:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Step 7: Create service to run .net WebApp in the background

First, we will create a service definition file:

sudo nano /etc/systemd/system/kestrel-webapp.service

Enter below configuration and make changes to hosting path as needed:

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

[Service]
WorkingDirectory=/var/www/demo_tekspace_io/net7.0/publish
ExecStart=/usr/bin/dotnet /var/www/demo_tekspace_io/net7.0/publish/DotNetWebApp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=rahil
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Save the file and enable the service:

sudo systemctl enable kestrel-webapp.service

Output:

rahil@ubuntu:~$ sudo systemctl enable kestrel-webapp.service
Created symlink /etc/systemd/system/multi-user.target.wants/kestrel-webapp.service → /etc/systemd/system/kestrel-webapp.service.

Next, we will start the service:

sudo systemctl start kestrel-webapp.service

Next, we will check the status of the service to make sure it has started successfully.

sudo systemctl status kestrel-webapp.service

Output:

rahil@ubuntu:~$ sudo systemctl status kestrel-webapp.service
● kestrel-webapp.service - Example .NET Web API App running on Linux
     Loaded: loaded (/etc/systemd/system/kestrel-webapp.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-08-31 19:57:03 UTC; 15s ago
   Main PID: 30099 (dotnet)
      Tasks: 16 (limit: 512)
     Memory: 22.3M
        CPU: 383ms
     CGroup: /system.slice/kestrel-webapp.service
             └─30099 /usr/bin/dotnet /var/www/demo_tekspace_io/net7.0/publish/DotNetWebApp.dll

Aug 31 19:57:03 ubuntu systemd[1]: Started Example .NET Web API App running on Linux.
Aug 31 19:57:03 ubuntu dotnet-example[30099]: info: Microsoft.Hosting.Lifetime[14]
Aug 31 19:57:03 ubuntu dotnet-example[30099]:       Now listening on: http://localhost:5000
Aug 31 19:57:03 ubuntu dotnet-example[30099]: info: Microsoft.Hosting.Lifetime[0]
Aug 31 19:57:03 ubuntu dotnet-example[30099]:       Application started. Press Ctrl+C to shut down.
Aug 31 19:57:03 ubuntu dotnet-example[30099]: info: Microsoft.Hosting.Lifetime[0]
Aug 31 19:57:03 ubuntu dotnet-example[30099]:       Hosting environment: Production
Aug 31 19:57:03 ubuntu dotnet-example[30099]: info: Microsoft.Hosting.Lifetime[0]
Aug 31 19:57:03 ubuntu dotnet-example[30099]:       Content root path: /var/www/demo_tekspace_io/net7.0/publish

If you see the service status as active (running), that means all your setup is configured successfully.

Before we end the tutorial, we need to make sure the Nginx has been reloaded to apply new changes we make to Nginx server block.

sudo systemctl reload nginx

Once the Nginx service has been reloaded, browse the app via the URL you set up in the server block. In my case, I will browse demo.tekspace.io.

asp.net webapp ui page screenshot from nginx server

You will see the HTML page load on your browser. If you see all the css and js file load correctly, that means the app is configured successfully to run on Nginx web server.

Leave a Comment

Scroll to Top