Demo guide for the deployment and hosting process of an ASP.NET Core MVC application on Ubuntu server with Nginx proxy. The guide will start with an already build app and the official Ubuntu [vps machine with root access]. Everything else required to host this application in a Production environment will be set up during the process.

These are the steps we will go through this post:

  • Secure ssh access
  • Web server(Nginx)
  • MySql server
  • Firewall
  • Automated security measures
  • Cloudflare integration

My project, in current view, consists of the following interesting points:

  • Aspnet core mvc
  • MySql for a backend database

In this guide are used the following: Tools used:

What you will need to follow along:

Later I will use ebrick as a username and ermir-net as a folder. Replace these values with your own.

Server preparation

In this section, we will prepare the server for normal access. Open PuTTY and login to your VM machine. After login you will see the welcome message of this brand new Ubuntu installation, along with the number of packages to be updated, speaking of which now it's time to run:

apt-get update
apt-get upgrade

The first command will update the package list, the last one will install them. Note that to install updates from packages that are not currently present you will need to execute

apt-get dist-upgrade

As this is the first time we update this OS, depending on your internet connection and power may take a bit to complete. Usually, this is not necessary but it's a good practice to check if a system reboot is required after we install updates, in ubuntu, a system restart is necessary if the file /var/run/reboot-required exists, and you can confirm that by executing:

ls /var/run/reboot-required

*** System restart required *** this is the file content in case you are curious about it.

execute reboot in your ssh interface, after this PuTTY goes inactive, close the window and wait a few seconds, then connect again using root user and its password.

Create a new user

It's never a good idea to access your machine using root credentials, for this we will create a new user, grant him superuser access, and all the other actions will be accomplished from that user. in fact, we will prevent the root user from ssh at all.

You create a new user with adduser <USER_NAME> command and choose the name correctly, soon you will find out that your logs are getting filled with common names (root, admin, john, etc)

adduser ebrick

Choose a strong password, and follow the questions to complete the other steps. To Enable this user to issue root commands when needed we use usermod -aG <GROUP> <USERNAME> where -aG option stands for append into Groups :

usermod -aG sudo ebrick

Verify the group of this user using: groups <USERNAME>

groups ebrick

Secure SSH access

We will add the new user into ssh allowed access list of users, and then prevent the root user from accessing this machine using ssh protocol. To accomplish this we will edit sshd_config file.

nano /etc/ssh/sshd_config

Find and replace or add the following configurations in this file:

Port 4743 # use whatever number you think, it's considered a best practice to be a high number.
PermitRootLogin no
LoginGraceTime 30
AllowUsers ebrick

Port is changed to a high number, there is a countless number of bots actively scanning random IP, and 22 should not be your port for ssh access.

PermitRootLogin this option will disable root access into ssh entirely, so one less thing not to worry about.

LoginGraceTime is the number of seconds the connection is kept open and waiting for the user to log in, after this, the connection will just be dropped, add a reasonable time.

AllowUsers <User1> <User2> <etc> Allows only a specific list of users to connect with ssh. Keep this list to your current needs.

Save and exit the editor.

Restart the ssh service: root@ermir-net:~# sudo systemctl restart ssh but DONT close the window yet, in case you have made a mistake (such as a port number you don't remember for instance.) you still have root access from the current window. Open a new instance of PuTTY and try to log in using the new user, password and the new port. In case the login was successful, you can exit the old window. It has completed its duty.

Install MySql

This step applies if you are running a MySql engine, if not, skip it.

Update apt package repository:

sudp apt update

Install the default package:

sudo apt install mysql-server

Run the security script, this will take you through a series of steps to change some security settings and adapt the MySQL server for your use:

sudo mysql_secure_installation

This script as of the time of writing will take you through:

  • Password strength
  • Disable root login
  • Remove test database
  • Remove anonymous access

I'm using MySQL workbench in my dev machine, so I'll need to take a backup of the current DB, and restore it in the server side.

Server > Data Export *select your db* and follow the wizard to export all the data and schema in a separate file.

Send the file in the server, I'm using Bitvise ssh client, and locating it under:


Login and Create the database and exit the MySQL interface:

sudo mysql
create database me_db;

Restore db schema and data from the backup file:

sudo mysql me_db < "/home/ebrick/ql/db-bak.sql"

at this point, the DB should be ready in this instance of the MySQL engine. one more thing left to do: Create a specific user that we will use to access this DB from our application:

create user 'appqluser'@'localhost' identified by '<PUT_YOUR_PASS_HERE>'

Grant the necessary permissions to operate the DB:

grant select, insert, update, LOCK TABLES on me_db.* to 'appqluser'@'localhost';

At this point the user should be able to have the necessary permissions for accessing this db. Test it with mysql command interface, and try to use and select some results from a table.

Remember to update the db connection string.

Install dotnet core

We will need to prepare the env for executing dotnet core applications, follow linux-package-manager website for correct package versions. For Ubuntu 18.04:

Register Microsoft package update sources

wget -q
sudo dpkg -i packages-microsoft-prod.deb

Install net core runtime

sudo add-apt-repository universe
sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install aspnetcore-runtime-2.2

Deploy the application

In this step, We will deploy the application and test it using kestrel. If everything goes as expected, we are ready to continue to the next step.

Open a command window in the project directory, that's the same location where <PROJECT_NAME>.csproj is located and publish this application using the Release configuration

dotnet publish -c Release

The default output should be similar to <YOUR_PROJECT_ROOT>\bin\Release\netcoreapp2.2\publish\

Consider organizing the server project locations in a way you won't need to guess where you put each project, yes after you deploy this web app, probably you will need to deploy other apps too in this same machine, so just keep in mind the simplicity.

Inside /var, I'll create another directory for all my net core applications. Can you guess its name? : netcore!

PuTTY window:

cd /var
sudo mkdir netcore

Inside this directory, create 2 other directories:

cd netcore
sudo mkdir console
sudo mkdir webapps
cd webapps
sudo mkdir ermir-net
cd /var

With the necessary directories in place, there is one more thing to do: Take ownership of this directory and everything it contains, so I will put this under my user, but sudo group.

sudo chown -R ebrick:sudo /var/folder/path/here

DO NOT FORGET TO REPLACE ermir-net or other names.

It's time to transfer our web application in the server, and now Bitvise ssh client helps in a very similar interface with FileZilla. Authenticate and take advantage of SFTP window feature to upload everything inside local \publish folder in /var/netcore/webapps/ermir-net

everything except web.config file! We don't do that here!

After upload process is finished, you can test the installation of dotnet packages ```dotnet <YOUR_PROJECT_NAME>.dll, a standard output showing the URL of the application and other environment information means the installation was successful.

Background service to keep the app running

dotnet <Project>.dll command is good for development purposes, but in a production environment, a background service should be used to manage the state of this program.

In Ubuntu, a Daemon is a program that executes in the background, think of a windows service. So let's create a daemon now to keep this app running.

We will use the systemd utility to manage the kestrel process, so in your terminal :

cd /etc/systemd/system

Here let's create a command file that will instruct the systemd what to execute, where to find the files, when to restart the process and so on. Remember to use a simple and memorable name, I prefer <PROJECT_NAME>.service. You will use this more than you think.

sudo nano ermir-net.service

Setup the file content to your current use, mine looks like this:

[Unit] aspnet core mvc website

ExecStart=/usr/bin/dotnet /var/netcore/webapps/Me.dll


  • Enable the service: sudo systemctl enable ermir-net.service
  • Check the service status: sudo systemctl status ermir-net.service

if not already running, you can try to start the service by executing:

sudo systemctl start ermir-net.service

if you check the service status again you will see something similar to my response:

ermir-net.service - aspnet core mvc website
   Loaded: loaded (/etc/systemd/system/ermir-net.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2018-12-21 09:50:13 CET; 6s ago
 Main PID: 12024 (dotnet)
    Tasks: 17 (limit: 1110)
   CGroup: /system.slice/ermir-net.service
           └─12024 /usr/bin/dotnet /var/netcore/webapps/ermir-net/Me.dll

If not, doublecheck the path to your application executable, if it runs using dotnet command it should work here too.

Install and configure Nginx

Make sure to see Ubuntu releases and find the codename according to your system. This codename will be used for nginx package names

For Ubuntu 18.04 LTS: Update the sources list:

sudo nano /etc/apt/sources.list.d/nginx.list

and add the following content into it, save and exit:

deb bionic nginx
deb-src bionic nginx

sudo apt update:

Note that an error about the key may come up, just follow 2 simple commands in nginx docs and you should be good to continue.

finally, install nginx:

sudo apt-get install Nginx
  • sudo systemctl status nginx Check nginx status
  • sudo systemctl start nginx Start the service if not started
  • Open your browser and hit the machine IP, you should see the nginx default welcome page.

Configure Nginx

Move to Nginx folder: cd /etc/nginx. if I list the content of this directory the old setup has changed, missing sites-available and sites-enabled. So I'll just create these directories:

Run sudo mkdir sites-available and sudo mkdir sites-enabled

We will create the configuration inside sites-available and add a symlink in sites-enbled.

Update the nginx.conf file, add include /etc/nginx/sites-enabled/*; inside http block, then remove the default.conf file inside conf.d directory, that is just a sample and we don't need it anymore. This will instruct Nginx to check for HTTP configurations under that directory. My example:

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

Delete default.conf file:

sudo rm /etc/nginx/conf.d/default.conf

After the previewous step, conf.d folder is empty, leave the include directive above untouched because we will use that folder later when integrating cloudflare.

Go inside sites-available and create the configuration file:

cd sites-available
sudo nano default

Insert the following content:

server {
    listen        80;
    server_name *;

    add_header X-Frame-Options "SAMEORIGIN";

    location / {
        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 $http_host;
        proxy_cache_bypass $http_upgrade;

Save, Close the editor and create a symlink to sites-enabled:

sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

Then check the nginx configuration for any syntax error:

sudo nginx -t, The response should be something similar to:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

This means it's safe to restart nginx, so: sudo systemctl restart nginx and then sudo systemctl status nginx. By this point, you should be able to access your site with your own domain. Congrats!

Choose www or non-www domain. Your resources should have only one endpoint. I prefer the URL of my domain to be without the www prefix. So to do that:

sudo nano /etc/nginx/sites-available/default and add the following server configuration at the top of the file:

server {
    return 301 $scheme://$request_uri;

Save and close the default file. Check the nginx configuration sudo nginx -t and then restart the nginx sudo systemctl restart nginx

Now if you access your domain with www prefix, nginx will issue a 301 Moved Permanently redirect to non-www version.

And this is the first part.

Make sure to follow the Secure your ubuntu server for the next steps.