Ermir Beqiraj
Backend architect. Systems, agents, infrastructure — from inside the work.
all writing

Demo guide for the deployment and hosting process of an ASP.NET Core MVC application on Ubuntu server with Nginx proxy. The guide starts with an already built app and an Ubuntu VPS 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:

  • Secure SSH access
  • Web server (Nginx)
  • MySQL server
  • Firewall
  • Automated security measures
  • Cloudflare integration

Tools used:

Later in this guide, ebrick is used as a username and ermir-net as a folder name. Replace these with your own.

Server preparation

Open PuTTY and log in to your VM. After login, update all packages:

apt-get update
apt-get upgrade

For packages not currently present, also run:

apt-get dist-upgrade

Check if a reboot is required after updates:

ls /var/run/reboot-required

If the file exists, reboot, wait a few seconds, then reconnect.

Create a new user

It’s never a good idea to access a machine using root credentials. Create a new user, grant superuser access, and then prevent root from SSHing at all.

adduser ebrick

Choose a strong password and complete the prompts. Grant sudo access:

usermod -aG sudo ebrick

Verify with: groups ebrick

Secure SSH access

Edit sshd_config:

nano /etc/ssh/sshd_config

Find and update the following:

Port 4743        # use a high non-standard port
PermitRootLogin no
LoginGraceTime 30
AllowUsers ebrick
  • Port — changing from 22 reduces automated scanning attempts
  • PermitRootLogin no — disables root SSH entirely
  • LoginGraceTime — drops idle connections after 30 seconds
  • AllowUsers — only the listed users can connect via SSH

Save and exit. Restart SSH: sudo systemctl restart ssh

Do not close the current window yet. Open a new PuTTY instance and log in as ebrick on the new port to confirm it works before closing the root session.

Install MySQL

sudo apt update
sudo apt install mysql-server
sudo mysql_secure_installation

The security script will guide you through:

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

Export your local DB from MySQL Workbench via Server > Data Export, transfer the .sql file to the server (e.g. via Bitvise SFTP) to /home/ebrick/ql/db-bak.sql, then restore it:

sudo mysql
create database me_db;
exit

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

Create a dedicated application user with minimal permissions:

create user 'appqluser'@'localhost' identified by '<PUT_YOUR_PASS_HERE>';
grant select, insert, update, LOCK TABLES on me_db.* to 'appqluser'@'localhost';

Test the new user with the MySQL CLI. Remember to update the connection string in your application.

Install .NET Core runtime

Follow the official Linux package manager guide for your exact Ubuntu version. For Ubuntu 18.04:

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 aspnetcore-runtime-2.2

Deploy the application

Publish the release build locally:

dotnet publish -c Release

Output will be in <PROJECT_ROOT>\bin\Release\netcoreapp2.2\publish\.

Create organized directories on the server:

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

Take ownership of the directory:

sudo chown -R ebrick:sudo /var/netcore

Upload everything from your local publish folder to /var/netcore/webapps/ermir-net via Bitvise SFTP. (Skip web.config — it’s not used on Linux.)

Test the deployment:

dotnet /var/netcore/webapps/ermir-net/<YOUR_PROJECT>.dll

A line showing the Kestrel listening URL means it worked.

Systemd daemon

dotnet in a terminal is fine for development, but production needs a managed background service. Create a systemd unit:

cd /etc/systemd/system
sudo nano ermir-net.service
[Unit]
Description=ermir.net aspnet core mvc website

[Service]
WorkingDirectory=/var/netcore/webapps
ExecStart=/usr/bin/dotnet /var/netcore/webapps/ermir-net/Me.dll
Restart=always
RestartSec=10
SyslogIdentifier=app-ermir-net
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable ermir-net.service
sudo systemctl start ermir-net.service
sudo systemctl status ermir-net.service

You should see Active: active (running) with the dotnet process listed.

Install and configure Nginx

Check Ubuntu releases for your codename. For Ubuntu 18.04 LTS (bionic):

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

Add:

deb http://nginx.org/packages/ubuntu/ bionic nginx
deb-src http://nginx.org/packages/ubuntu/ bionic nginx

If a key error comes up during sudo apt update, follow the two commands in nginx docs.

sudo apt update
sudo apt-get install nginx
sudo systemctl start nginx

Opening your server IP in a browser should show the nginx welcome page.

Configure nginx for your app

Navigate to /etc/nginx. If sites-available and sites-enabled directories are missing, create them:

sudo mkdir sites-available
sudo mkdir sites-enabled

Update nginx.conf to include sites-enabled inside the http block:

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

Remove the default sample config:

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

Create the site config:

cd /etc/nginx/sites-available
sudo nano default
server {
    listen        80;
    server_name ermir.net *.ermir.net;

    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;
    }
}

Create a symlink to sites-enabled:

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

Test the config and restart:

sudo nginx -t
sudo systemctl restart nginx

At this point you should be able to access your site at your domain.

Enforce non-www

Add a redirect at the top of the default config file:

server {
    server_name www.ermir.net;
    return 301 $scheme://ermir.net$request_uri;
}

Check config and restart nginx. Any www. requests will now permanently redirect to the apex domain.

That covers the core deployment. For firewall, fail2ban, and security hardening, see the follow-up article on securing an Ubuntu server.

Ermir Beqiraj is a backend architect building AI-integrated infrastructure. This is his personal writing.