Stuart Fulton

Setting up Linux for an ASP.NET Core application

Posted by stuart on Tuesday, July 31, 2018

Microsoft's open source ASP.NET Core framework is cross-platform, but many official tutorials assume you'll be serving your site with Azure or IIS. These notes show you how I set up an ASP.NET Core site on Linux.

In order to launch a site with these steps, you need a domain name whose DNS entries have been pointed at your server's IP address. I used the instructions at Namecheap.

Write an ASP.NET Core application

This post focuses on deploying an ASP.NET Core site to Linux, and assumes you have already written one that works on your machine.

Get a virtual private server

A virtual private server is a sandboxed portion of a physical server that behaves like an independent machine. It gives you complete control over how your environment is set up, even to the point of installing the operating system from scratch. I chose Linode because of their reputation. My plan is $10/month, but they go as low as $5.

I get a referral credit if you sign up for Linode through this link.

Install Linux tools for Windows

Since I develop on Windows 10, I installed software that makes Linux command line tools available from the Powershell console. The first step is to go to Windows Features and enable Windows Subsystem for Linux, and the second is to install the Linux distribution of your choice from the Windows Store.

Information on installing the Windows Subsystem for Linux is available here:
https://docs.microsoft.com/en-us/windows/wsl/install-win10

Prepare names and passwords

For setup, you will need:

  • A password for your internet hosting account (i.e. Linode)
  • A password for your Linux root user
  • A username and password for your Linux non-root user
  • A password for the ssh connection to your server
  • A name for the server itself
  • The IPv4 and IPv6 address of your internet hosting account

If you are setting up a Postgresql database, you will also need:

  • A name for the Postgres database your application will use
  • A username and password for the Postgres database user your application will use

Try to find a good balance between the length of your passwords and how easy they are to remember, particularly for the ssh and non-root Linux passwords, which you'll be using a lot.

Passphrases are stronger and more memorable than short complicated passwords.

Install an operating system on your server

After creating your Linode account, log in and click on the first (and only) Linode. On the Dashboards tab, click Deploy an Image. Select Ubuntu 18.04 in the Image dropdown, and enter your root password in the password field, then click Deploy. Wait for the process to complete, then click boot.

This is covered in depth in Linode's getting started guide.

Log in as root

Wait for the server to boot, then in Powershell, type:

bash

This will get you to a Linux compatible prompt where you can use programs from the Linux toolchain. At the new prompt, type:

ssh root@8.8.8.8

8.8.8.8 is an example IPv4 address. You are meant to replace it with your own IPv4 address throughout this document. Your own IPv4 address can be found in the dashboard of your Linode instance.

If ssh fails...

If you've already worked through some of these steps, the ssh command may warn you that the remote host identification has changed. In that case, enter the command the error message suggests, which should look something like this:

ssh-keygen -f "/home/yourusername/.ssh/known_hosts" -R 8.8.8.8

It will warn you that the authenticity of the remote host cannot be established. Type yes anyway. If you had to do this step, remember to try the connection again:

ssh root@8.8.8.8

Set up Ubuntu

Once logged into the server as the root user, type these commands to update Ubuntu, get security updates automatically, and install a utility to restart your software when the server reboots:

apt-get -y update && apt-get -y upgrade
apt-get -y install unattended-upgrades supervisor

During the installation, you may be asked questions about the bootloader program, GRUB. If so, use the arrow keys and spacebar to select and enter that you would like to keep the version of GRUB already installed. On the next screen, choose to install it on any disk that is not a boot partition. When you return to a command prompt, type these commands to set the server name and timezone:

hostnamectl set-hostname myhostname
timedatectl set-timezone UTC

Choosing UTC as the timezone simplifies some date calculations. The hostname is an arbitrary name of your choice. No one sees the hostname but you.

Set up the hosts file, install Nginx and Postgresql

On the command line, type:

nano /etc/hosts

Then in the editor that opens, add the following lines to the bottom of the file to associate your server's IPv4 and IPv6 addresses with your domain name and server name:

8.8.8.8 myhostname.mydomainname.com myhostname
2001:4860:4860::8888 myhostname.mydomainname.com myhostname

2001:4860:4860::8888 is an example IPv6 address. Replace it with your own IPv6 address, which can be found in the dashboard of your Linode instance.

Exit and save using the commands at the bottom of the editor, and press Enter to keep the current filename. Then type the following on the command line to install Postgresql and Nginx:

apt-get -y install nginx postgresql postgresql-contrib

Install the ASP.NET Core runtime

Enter the following commands to add Microsoft's ASP.NET Core repository and install the runtime:

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget -q https://packages.microsoft.com/config/ubuntu/18.04/prod.list 
mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
chown root:root /etc/apt/sources.list.d/microsoft-prod.list
apt-get install apt-transport-https
apt-get update
apt-get install aspnetcore-runtime-2.1

Set up the firewall (UFW) and install Fail2Ban

UFW prevents your server from being accessed except in the ways that you need (in this case: ssh connections, port 80, and port 443), and Fail2Ban helps stop brute force password attacks and denial of service attacks. Configure UFW with the following commands:

ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

If a message asks you if you'd like to proceed despite the fact that it could disrupt existing ssh connections, type yes. Now install Fail2Ban and reboot:

apt-get -y install fail2ban
shutdown -r now

Create a new user

Log into the server again from your Powershell bash prompt:

ssh root@8.8.8.8

Add a new user. You will be asked to provide a password.

adduser myserveruser
usermod -aG sudo myserveruser

This username is something you choose, as is the password you'll be asked to provide.

Since you are currently logged in as root, log out so you can log back in as the new user.

exit

Set up ssh keys

Having logged out of the server, type the following command:

ssh-copy-id myserveruser@8.8.8.8

Log back into the remote machine, this time as the new user:

ssh myserveruser@8.8.8.8

You will be asked to provide first the password you chose for this new user, and then a passphrase of your choosing that you will use whenever you connect to the remote server with this ssh connection. Once you are logged in, edit this ssh configuration with the following command:

sudo nano /etc/ssh/sshd_config

In the sshd_config file, comments are preceded by #. Remove the # character to make a commented line active.

Ensure that the file contains the following three lines, changing the values of existing lines if necessary:

PermitRootLogin no
PasswordAuthentication no
AddressFamily inet

Set up Postgresql database and user

This setup is only necessary if your project reads from a database.

Run psql as the user "postgres", which is the built-in administrative user for Postgresql:

sudo -u postgres psql

This brings you to a command prompt just for the Postgresql database. Execute these three lines to create a database and a non-administrative user with permission to use it:

CREATE DATABASE mydatabase;
CREATE USER mydatabaseuser WITH PASSWORD 'myaw3som3passw0rd';
GRANT ALL PRIVILEGES ON DATABASE mydatabase TO mydatabaseuser;

The database user you are creating here is different from a Linux user, so you can give it the same name as your Linux non-root user if you prefer.

Exit psql:

\q

Set up app environment

Change to the home directory of myserveruser:

cd ~/

Create a directory to hold your application:

mkdir myapplication

Depending on how your application works, you can create environment variables to be read when the application launches, or create an application.json file that lives outside the application directory, which is my own preference.

Learn more about ASP.NET Core configuration from Microsoft's documentation.

Set up Nginx

Go to the Nginx sites-enabled directory:

cd /etc/nginx/sites-available

Type the following command to create and edit a new Nginx configuration file for your site:

sudo nano ./mydomainname.com

In this file, type the following code block:

server {
	listen 443 default_server ssl;
	server_name     mydomainname.com        www.mydomainname.com;
	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;
	}
}

Publish your ASP.NET Core project

First, log out of the server.

exit

In the root of your ASP.NET project, publish for release:

dotnet publish -c Release

From your Powershell bash prompt, move to the parent directory of the publish folder you just created (in my case: projectRoot/bin/Release/netcoreapp2.1) and type the following command to copy the publish folder to a directory within your user folder on the server:

scp -r ./publish/ sfbloguser@8.8.8.8:~/myapplication/

Enable the sites in Nginx

Go to the Nginx sites-enabled directory:

cd /etc/nginx/sites-enabled

Make a symbolic link to the file you created in sites-available (if you want to disable this site later, just delete the site in sites-enabled, and it will remain in sites-available):

sudo ln -s ../sites-available/mydomainname.com

Reload Nginx so it will read the new configuration:

sudo service nginx reload

Create script to make the site launch on boot

Go to the application directory:

cd ~/myapplication

Create and edit a file named init.

nano init

In the editor, type the following lines:

#!/bin/bash
cd /home/myserveruser/myapplication/publish
dotnet myapplication.dll

The .dll file on the third line should be the file with the same name as your ASP.NET project that lives in the publish directory that you copied to the server with the scp command.

Save the file using the commands on the bottom of the editor. Then on the command line, make the file executable:

sudo chmod -x ./init

The commands in this file will run on startup, but not without being called by supervisor, which we installed earlier. Go to the supervisor configuration directory:

cd /etc/supervisor/conf.d

Create and edit a new file called myapplication.conf:

sudo nano sfblog.conf

In the editor, type the following lines:

[program:myapplication]
command=/home/myserveruser/myapplication/init
autostart=true
autorestart=true
stderr_logfile=/var/log/sfblog.err.log
stdout_logfile=/var/log/sfblog.out.log

On the Linode dashboard in your browser, click the Reboot button. Wait a few minutes, and navigate to your site in a browser. If all has gone well, your site is live and your server has basic security.