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:
- .NET Core 2.2
- MySQL (5.7)
- VPS with Ubuntu 18.04 LTS x64
- PuTTY
- Bitvise SSH Client
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 attemptsPermitRootLogin no— disables root SSH entirelyLoginGraceTime— drops idle connections after 30 secondsAllowUsers— 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.