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:
- dotnet core 2.2
- MySql (5.7)
- VPS with Ubuntu 18.04 LTS x64
- PuTTY
- Bitvise SSH Client
What you will need to follow along:
- Root access to a Ubuntu server
- An already build aspnet core application
- PuTTY
- Bitvise SSH Client
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:
/home/ebrick/ql/db-bak.sql
Login and Create the database and exit the MySQL interface:
sudo mysql
create database me_db;
exit
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 https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
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]
Description=ermir.net aspnet core mvc website
[Service]
WorkingDirectory=/var/netcore/webapps
ExecStart=/usr/bin/dotnet /var/netcore/webapps/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 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 - ermir.net 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 http://nginx.org/packages/ubuntu/ bionic nginx
deb-src http://nginx.org/packages/ubuntu/ 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 statussudo 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 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;
}
}
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 {
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
Save and close the
default
file. Check the nginx configurationsudo nginx -t
and then restart the nginxsudo 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.