Hosting a .Net Core App with Nginx and Let's Encrypt
Tags: dotnet, nginx, csharp, beginners
Published: 2019-05-07 16:50:34

---
title: Hosting a .Net Core App with Nginx and Let's Encrypt
published: true
description: It's built, let's get it out there!
tags: dotnet, nginx, csharp, beginner
---

[In the last post](https://dev.to/ianknighton/event-registration-web-app-with-net-core-and-stripe-39ib), we built a simple .Net Core application to handle payment processing for our event registration. It works like a champ on our local machine, but it doesn't do us any good if no one can access it.

Today, we'll make it available to the public.

I mostly started with this helpful article in the [Microsoft Docs](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-2.2). However, I found some issues that I had to work around.

## A Quick Update to the App

Using Nginx, we'll need to be able to reverse proxy, so we need to add a little bit of code to our project to handle this. In `startup.cs`, we'll add to the `Configure` method.

```csharp
app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });
```

You may need to add to your using statements.

```csharp
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
```

Now we can carry on to building out the server.

## Setting Up the Server

I started with a [Linode](https://www.linode.com/?r=bddbeba21bd71930c65c4a52ab8d6426acd3c1d7) "Nanode" server with Ubuntu 18.04 installed. In my case, this page only needs to be available for three months and won't see a ton of traffic, so the small size and smaller cost ($5 a month) really suits what I'm trying to do.

Go through your normal setup (update, upgrade, secure ssh, etc...) and then we'll install the other pieces we need.

### Installing Dotnet on the Server

Again, I referenced [Microsofts Docs](https://dotnet.microsoft.com/download/linux-package-manager/ubuntu18-04/sdk-current) for this, but I condensed the commands together for the sake of brevity.

```console
wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo add-apt-repository universe
sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install dotnet-sdk-2.2
```

### Copying Code and Building 

Depending on how intense you may want to go with this, you may want to build out a full CI pipeline. In my case, I'm not going to be doing any iterations, so I just copied the code over using `scp` and ran it on the server.

```console
scp -r ~/path/to/directory ssh@server:~/appname
```

That command (once populated with your information) should copy the entire folder from your machine to the server in the SSH users home directory. 

SSH into the server and build/publish the application.

```console
dotnet build
dotnet publish --configuration Release
```

Publish should move everything into a folder in the `bin/Release/netcore2.2/` directory.

Once that is done and successful, I then use symbolic linking to put it in `/var/www`. It's not necessary to do it, but it's an old habit.

```console
sudo ln -s ~/appname/bin/Release/netcore2.2/publish /var/www/appname
```

This links the folder in the home directory to the `/var/www/` directory, so if you do need to make fixes, there's no worry about copying things around.

### Installing and Setting Up CertBot

Certbot is pretty straight forward and it's a beautiful tool I've written a bit about.

First, install through `apt`.

```console
sudo apt install -y certbot
```

Once that is done, we can request a certificate. I prefer to use the `standalone` method.

```console
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
```

Once that is finished, you should be able to run `sudo certbot certificate` and see the location of the certificate on your machine.  It's important to keep this somewhere, we'll need it in the next step.

### Install and Configure Nginx

We have code, we have a certificate, now we just need to make it available to the adoring masses.

First, install Nginx:

```console
sudo apt install nginx
```

Second, make sure you can access it through your firewall. Assuming you're using `ufw`, we can just update the permissions.

```console
sudo ufw allow 'Nginx Full'
```

Now, if you navigate to the IP address of your server, you should see the "welcome to nginx" screen.

Once you've verified that you're able to access the server, you'll want to make sure your domain is pointed to the IP. This is going to vary wildly based on what your DNS provider is, but I believe in you.

With a domain, we can now handle setting up Nginx for our site. I'll save the boring details of how all of this works, but here's what we need to do.

Create a file in `/etc/nginx/sites-available/`:

```console
sudo nano /etc/nginx/sites-available/yourdomain.conf
```

In the text editor, copy in this configuration:

```console
server { 
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443;
    server_name yourdomain.com;

    ssl_certificate           /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    ssl on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    gzip  on;
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_types text/plain text/html text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml;
    gzip_buffers 16 8k;
    gzip_disable “MSIE [1-6].(?!.*SV1)”;
    
    access_log  /var/log/nginx/yourdomain .access.log;
    
location / {
    proxy_pass            https://localhost:5001;        
    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;
    } 
}
```

Again, I'm being brief here, but this should do everything we need to take requests to `yourdomain.com`, forward them to SSL, and connect them to the application running on the server.

In order to enable it, we'll make another symbolic link and then restart Nginx.

```console
sudo ln -s /etc/nginx/sites-available/yourdomain.conf /etc/nginx/sites-enabled/yourdomain.conf
sudo systemctl restart nginx
```

To make sure everything is working, we'll start the app on its own and try to access it through the browse.

```console
dotnet run /var/www/yourapp/yourapp.dll
```

After you've verified that this works, you can close the application. The next step is to make it working on its own.

### Setting up systemd

Yes, there is some [tragedy](https://www.youtube.com/watch?v=o_AIw9bGogo) around `systemd`, but it's still handy.

Create a `.service` file for you application:

```console
sudo nano /etc/systemd/system/yourapp.service
```

Add the following information to the file:

```console
[Unit]
Description=Event Registration Example

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

[Install]
WantedBy=multi-user.target
```

The only thing I changed here from the Microsoft Doc was removing the `User=www-data` line. While it is best practice to have a limited scope user, I didn't have a need to worry about this service running as root. This is a non-critical system that handles next to nothing other than a redirect, so I thought it was safe enough.

Now we can enable and start the service through systemd.

```console
sudo systemctl enable yourapp.service
sudo systemctl start yourapp.service
sudo systemctl status yourapp.service
```

If everything has gone to plan, you should receive a status of running and be able to access your page. If it has all gone pair shape, you can still access the logs.

```console
sudo journalctl -fu yourapp.service
```

*That's It!*

Hope this helps. There are a lot of ways this can be optimized or better secured, but for something this simple I didn't sweat it too hard. It's been running for a couple weeks and processed a handful of payments from people we gave early access to.

I did make some subtle styling changes, but that's about it. Works perfect and is relatively easy to modify going forward.

Thanks for coming along on the ride!