How to host an Asp.NET core 3.1 application on Linux Ubuntu 20.04 with Apache as reverse proxy

Host an Asp.Net Core Website in Linux without Docker

Vincenzo Pucarelli
7 min readNov 17, 2020

Nowadays there are plenty of tutorials on how to host .net Core applications on containers using Docker and precooked images. I have been struggling a lot to find a guide on how to host an Asp.NET core website on Linux (Ubuntu) with command line only.

The official guide from Microsoft explains only how to run it on Cent OS but Ubuntu for many things it’s easier and more straightforward.

Why should I do this when there are containers? The reason it’s simple: for testing purposes and because of the wonderfully free Oracle Cloud free tier which provides you two “forever free” machines with 50 Gb of disk and 1 Gb of RAM each, which is enough to run a bunch of web application and services, free of charge.

More information here:

For writing this tutorial I used one of their machines so it is tested and “production-ready”, but I will skip the registering part and starting where you have already entered or had access to the machine SSH.

To connect to the machine via SSH I use the nice Bitvise SSH client which comes in handy because it has an SFTP client integrated and you can type commands and transfer files on the machine without hassle.

Preliminary steps: Update the machine

In this step, I just update the machine to have the last packages and updates

Every command it’s executed with sudo for not having problems so I suppose you have root access to launch commands, scripts, and so on.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
sudo reboot

Installing the necessary components

After updating and rebooting the machine we will install all the necessary components to run our application including the Asp.NET core framework (3.1), Apache, and the extensions needed to use it as a reverse proxy

Reverse proxy schema
sudo wget -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y apt-transport-https
sudo apt-get update
sudo apt-get install -y aspnetcore-runtime-3.1
sudo apt-get install -y apache2
sudo apt-get install -y nano

In order to make apache work as expected, we need to install modules for proxy and headers with the following command

sudo a2enmod ssl headers rewrite proxy proxy_http proxy_html

Opening the ports

Ubuntu comes with an esoteric “Uncomplicated Firewall” app that should help you to configure the firewall easily. Let me say that for not Linux-friendly users like me this is not easy at all, and made mi struggle a lot, so I found that the easiest solution is editing the iptables directly and restart the machine.

Probably there are plenty of ways to do that but simply, for me, this was the fastest and easiest way to achieve it. Ubuntu comes with the HTTP and HTTP(s) ports closed so we can open them by adding these two lines on top of everything in the /etc/iptables/rules.v4 file. We can edit the file with the following command and add these two lines and we can save the file from nano with CTRL+X

sudo nano /etc/iptables/rules.v4

-A INPUT -p tcp -m multiport — dports 80,443 -m conntrack — ctstate NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -p tcp -m multiport — dports 80,443 -m conntrack — ctstate ESTABLISHED -j ACCEPT

sudo reboot

Publishing the app

If you arrived here this is the easy part: now we need to publish our .net Core Web App. For doing it so we need to publish it Framework Dependent and Portable (or Linux it works anyway)

Publishing the web app

Once done we just need to copy it to the machine in a folder in /var/www which is the default folder for published websites: let’s say /var/www/mywebsite. I used the same Bitvise with the Sftp Client to do it.

This part is important, I create the folder /var/www/mywebsite from the command line and I assign my user the ownership and the group of Apache which is the default one used to run the application with these commands. My user is named ubuntu and the user group used by Apache is called www-data by default

sudo mkdir /var/www/mywebsite
sudo chown -R ubuntu:www-data /var/www/mywebsite

After this, you can upload your website without problems in the current folder /var/www/mywebsite

Now we can assign permission of execution to the folder with this command

sudo chmod --verbose -R 750 /var/www/mywebsite

The 750 permission assigns full permissions to the owner (read, write, execute) and read and execute permissions to the group (the one used by Apache). If you need instead that the Web Application writes in the main folder or in a subfolder) you just need to change it to 770. Just remember to apply before the more restrictive permission to the whole folder and then the 770 to the subfolder only if you want to write only in a subfolder. This helps security.

Adding Kestrel service to run our application

I’m not explaining what is Kestrel in this article, I just say it’s the default .NET Core Web Server, it’s not production-ready and that’s why we use Apache as reverse proxy on top of it. Kestrel’s launched with the dotnet command and in my case, I’m changing the default port (5000) so you can add more websites with the same system, changing the port obviously for each website.

This part is covered pretty nicely in the official guide too but I just copy it here with the settings we are using to be as easy as possible for you:

We create a file to run the service and register it so it runs every time the machine is restarted

sudo nano /etc/systemd/system/kestrel-mywebsite.service

And copy this content

Description=My Web App
ExecStart=/usr/bin/dotnet /var/www/mywebsite/The.App.Entrypoint.dll --urls "http://localhost:5100"
# Restart service after 10 seconds if the dotnet service crashes:

Now you can install the service, launch it and check the status with these commands: If everything went fine you should see something like this:

sudo systemctl enable kestrel-mywebsite.service
sudo systemctl start kestrel-mywebsite.service
sudo systemctl status kestrel-mywebsite.service

If something went wrong (it happens to me dozens of times don’t worry!) you can check your service with these commands and when you detect the problem you need to stop the service, disable it, modify the service file and start again: enabling and starting it again…

sudo journalctl -fu kestrel-mywebsite.service #Checks the events of your servicesudo systemctl stop kestrel-mywebsite.service #Stop the service
sudo systemctl disable kestrel-mywebsite.service #Disable it
# You can even launch the website manually to see if it works with the same command that the service is launching and see if you had runtime errors#Go to your website folder
cd /var/www/mywebsite
#Launch the website as the service does with the dotnet command
/usr/bin/dotnet /var/www/mywebsite/The.App.Entrypoint.dll --urls "http://localhost:5100"

Configure Apache to call Kestrel when a request is done

For this step, I suppose you have a domain name that you want to use with your website so we need to create an Apache website which basically is a .conf file in a specific folder and we copy the content of this file. It’s very important we don’t get wrong with the folder because Apache has sites-available and sites-enabled: we use sites-available and after we enable the website in the proper way

sudo nano /etc/apache2/sites-available/mywebsite.conf

The Apache reverse proxy magic file

<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass /
ProxyPassReverse /
ServerAlias *
ErrorLog ${APACHE_LOG_DIR}mywebsite-error.log
CustomLog ${APACHE_LOG_DIR}mywebsite-access.log common

Once done we can just check that the Apache configuration it’s ok and we should see a message like this. After that, if the check goes fine, we can activate the website with the command below

sudo apachectl configtest
sudo a2ensite mywebsite.conf #enable the website

Now we just need to reload the Apache configuration and restart the service

sudo systemctl reload apache2
sudo systemctl restart apache2

If everything goes fine we should see the website in the URL we specified: et Voilà!

The website Working!

If something is not working it’s time to debug! As I spent hours doing it I hope this guide will help you in this process. If perhaps you see the default Apache webpage there is something not working with your Virtual Host configuration. On the other hand, if you don’t see anything or you see an error, it means you reached the Kestrel Web server and your .net Core application it’s just not working!

Note that in my configuration I’m using Cloudflare (the best DNS and caching service) to protect my website with HTTPS but connecting to the web server's 80 Port with a setting that is called Flexible Encryption. This means users will still see the website under HTTPS even if the server exposes only the HTTP port. In the next article if someone is interested I will show how to activate the SSL certificate to obtain full encryption and that all the communications are encrypted between the user and the server




Vincenzo Pucarelli

Project Manager and former Architect & Developer specialized in Microsoft technologies since 2004. Living in Madrid, Spain. Techie photographer, travels, wines.