Sunday, April 4, 2021

Host ASP.NET Core on Linux with Apache and Kestrel

Since the publication of new release of dotnet (core) v.5 there are several articles about how to setup and run a production environment in a Linux server. To be honest, most of them requires some modifications in order to implemented correctly. Thus, I decided to write a guide on how to setup, in an existing CentOS 7 Linux server, ASP.Net core (v.5) using the following configuration:

  1. Apache web server (used as a reverse proxy).
  2. Kestrel web server, serving the ASP.Net pages.
  3. DotNet Core v.5.

A proxy server forwards client requests to another server usually from an internal network (for example a LAN) to an external and usually untrusted network (usually the Internet). A reverse proxy works the opposite way: forwards requests that accepts from an external and usually untrusted network (usually the Internet) to an internal network, and specifically in our case, to the Kestrel server. 

I am not going to say much about Kestrel, since there are more than adequate articles about it out there. It is a Microsoft lightweight web server for ASP.NET core applications. Since it is very lightweight and not as functional as the well known web servers Apache, NginX and IIS, we usually put (in front) a well known web server to 'phase' the internet traffic and forwards (as a reverse proxy) the asp.net requests to the Kestrel.

Suppose that we have a (clean - text mode) CentOS 7 server box. In my case, I just set it up automatically from the control panel of my VPS provider. Actually it is very easy and cheap these days (personally I use virmach.com) to host a VPS server online with a Linux OS. Please note that I am not going into details of how to set the OS up, since it is out of my current scope.

Setup Apache as a reverse proxy

First of all we need to be sure that all of the installed packages are updated to their latest version, so we run the following command:

$ sudo yum update -y

Then, we install Apache web server using yum package manager:

$ sudo yum -y install httpd mod_ssl

Now, we have to configure Apache to work as a reverse proxy, i.e. to forward its requests to a different port (aka service) that the Kestrel server is listening, that is port 5000. To do this, we need to set up (create) a configuration file (that represents our application configuration file) in the well know location that Apache manages its configuration files, the: /etc/httpd/conf.d/
Suppose our application is called: demoapp, thus we enter the following:

$ sudo vi /etc/httpd/conf.d/demoapp.conf

Note: if you not an old-school and you don't like vi (aka nano-lovers) you can use an editor of your choice (like nano)... ;) 

We put into the file the following information:

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ErrorLog /etc/httpd/logs/demoapp-error.log
    CustomLog  /etc/httpd/logs/demoapp-access.log common
</VirtualHost>

<VirtualHost *:443>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ErrorLog /etc/httpd/logs/demoapp-error.log
    CustomLog  /etc/httpd/logs/demoapp-access.log common
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
    SSLCertificateFile /etc/pki/tls/certs/localhost.crt
    SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>


Now, just run the following command to check the syntax of the above configuration:

$ sudo service httpd configtest

The response should include the following:
Syntax OK
Don't worry (for now) in case that you get a warning like the following:
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using <YOUR_SERVER_IP>. Set the 'ServerName' directive globally to suppress this message

Now, just restart Apache and also set it to run at system start-up:

$ sudo systemctl restart httpd
$ sudo systemctl enable httpd

Important note on SELinux system

By default, SELinux prevents Apache from initiating connections, so it is unable to proxy requests to Kestrel Server. Thus, it is possible to get an Access Denied error when Apache tries to communicate with Kestrel: You will see the error as 503 "Service Unavailable" on your web browser (something like  seen in the following image).
 


To make Apache to allow connections, run the following command on the server :

$ /usr/sbin/setsebool -P httpd_can_network_connect 1

as shown in the following image (don't worry about the error messages) :


Install the .Net SDK

We are following the Microsoft instructions here to install the Net SDK. In this example we will choose to install the SDK (that includes the RunTime). 

We run the following command to add the Microsoft package signing key to our list of trusted keys and add the Microsoft package repository:

$ sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm

The .NET SDK allows us to develop apps with .NET. If we install the .NET SDK, we don't need to install the corresponding runtime. We enter the following command to install the .NET 5:

$ sudo yum install dotnet-sdk-5.0

Then, we can run the dotnet --version command to test that the .Net 5 is successfully installed:



Create an ASP.NET core Application

Ok, now it's time to create our demo application. 

We can create and compile the application in a Windows or in a Linux system, in the same or in a different machine but we must publish it in  /var/www/demoapp/ on this machine.

For simplicity let's create our demoapp application in this server. All we need to do, is typing the commands shown in the following picture:

































As you can see our default demoapp was publish in  /root/sources/apps/demoapp/bin/Debug/net5.0/publish/ catalog. We only need now, to copy it to the /var/www/demoapp/ and restart Kestrel.

$ cp /root/sources/apps/demoapp/bin/Debug/net5.0/publish /var/www/demoapp


Create the Kestrel service for our demoapp

Now, we have to create the service that will runs our demo application on Kestrel web server. 
To do this, we create a file called kestrel-demoapp.service in /etc/systemd/system

$ sudo vi /etc/systemd/system/kestrel-demoapp.service

and we enter the following info:

[Unit]
Description=Example .NET Web API App running on CentOS 7

[Service]
WorkingDirectory=/var/www/demoapp
ExecStart=/usr/bin/dotnet /var/www/demoapp/demoapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target


Now let's set to start the service every time we boot the system:

$ sudo systemctl enable kestrel-demoapp.service

and just start the service:

$ sudo systemctl start kestrel-demoapp.service

...and verify it is running:

$ sudo systemctl status kestrel-demoapp.service



Note: Each time you publish a new application or publish modifications to an existing one, you have to restart the Kestrel service as follows:
sudo systemctl restart kestrel-demoapp.service

You can also check by yourself if Kestrel works as expected, by using a line web browser (ideal for text mode environments) as lynx (you will need to install it first by typing "sudo yum install lynx"), by entering the following:

$  lynx 127.0.0.1:5000

If everything is fine, you should see the following:





$ sudo systemctl restart kestrel-demoapp.service

and visiting your web site, you must see:



Happy Programming!!