How to Deploy a ASP.NET Core 2 Web Application to Linux CentOS – Tutorial A detailed step-by-step guide explaining how to publish and deploy a ASP.NET Core 2.1 Web Application to a Linux CentOS machine - also working for Ubuntu, Fedora, Debian, RHEL and more

How to Deploy a ASP.NET Core 2 Web Application to Linux CentOS - Tutorial

In this post I will explain how to setup and deploy a production-ready ASP.NET Core Web Application on a NGINX-Powered Linux CentOS 7.x server. These instructions have also been tested with all major Linux distributions, such as Ubuntu, Fedora, Debian and RHEL.

The ASP.NET Core version I used to write this guide is  .NET Core 2.1.2 Runtime, which is the most recent one at the time of writing: however, there’s a high chance that they will work for newer ASP.NET Core versions as well.

Introduction

Before starting up, it can be wise to quickly explain how we’ll get it working. As you might already know, the preferred way to host a .NET Core app on Linux is to setup a Kestrel service on the Linux machine that will serve our web app on port 5000; once done, we will setup a NGINX instance that will act as a reverse proxy, forwarding the incoming requests to the Kestrel web server. This pattern is called edge-origin proxy and we already talked about it in this other post.

Here’s a simple diagram that might help you to understand how it will work:

How to Deploy a ASP.NET Core 2 Web Application to Linux CentOS - Tutorial

If we look at the above picture, we can immediately see how there are a lot of advantages in using such approach:

  • Scalability: the Reverse Proxy and the Web Server can be installed on the same machine or in different machines within the same environment/web farm (or even among different web farms). We can also have multiple web servers, depending on how much we want (or need) to scale the web application: the NGINX reverse proxy will be an excellent load balancer, as we explained here.
  • Data Security: the edge-origin pattern helps to shield the Web Server machine (or process) against malicious attacks, since it only opens the port 80 on the internal network. The only exposed machine (or process) is the Reverse Proxy, which is not the one actually executing the back-end code and therefore less vulnerable to external attacks.
  • Performance: the Reverse Proxy can be configured to conditionally cache content from the back-end servers, thus eliminating the need to connect to the web server at all for many requests. This is great for performance and can also shield the web servers from DDoS attacks and other brute-force malicious attempts.
  • Versatility: although this post presents a Linux-only install, this pattern will allow you to efficiently and transparently mix-in Linux and Windows servers: the web server(s) might as well be an IIS-powered Windows Server instead of the Linux Kestrel service we’ll be using in a short while.
For additional info and further details regarding the NGINX-powered edge-origin pattern, we strongly suggest to take a look to our NGINX article series.

In order to implement this pattern on a single Linux CentOS machine, we need to do the following:

  • Prepare the .NET Core Web Application by adding the required set of middlewares to support the reverse proxy feature.
  • Install and Setup the .NET Core runtime to the Linux CentOS machine.
  • Setup & configure the Kestrel web server service to serve our .NET Core web app on TCP port 5000.
  • Setup & configure the NGINX reverse proxy service to forward the HTTP requests received on TCP ports 80 and/or 443 to the Kestrel web server on TCP port 5000.

As a side step, we’ll also need to configure a process management tool to help restart the web app in case of crashes.

Prepare the Web Application

The first thing we have to do is to ensure that our web application will be properly configured to be served through a reverse proxy. In order to do that, we need to use the Forwarded Headers Middleware from the Microsoft.AspNetCore.HttpOverrides package. The middleware will use the  X-Forwarded-Proto header to update the Request.Scheme, ensuring that redirect URIs and other security policies will work properly.

To install the middleware, open your Setup.cs file and put the following code in the Startup.Configure method:

IMPORTANT: As written in the comments, this middleware must be put BEFORE calling UseAuthentication or other authentication scheme middlewares.

Once done we can switch to our Linux CentOS machine, where we’ll perform most of the remaining tasks.

Install the .NET Core runtime

The next thing to do is to install the .NET Core 2.1 runtime package on our Linux CentOS machine. This is the only package we need for apps deployed to production environments: the  .NET Core SDK package, for example, is not required at all.

The next steps will take for granted that you’re using a Linux CentOS 7.x environment: if you’re using something else, you might want to take a look at the .NET Core 2.1 Supported OS Versions for the complete list of .NET Core 2.1 supported operating systems, distributions and versions, and see if you will find a dedicated guide for your specific scenario.

Add the dotnet product feed

Before installing .NET, you’ll need to register the Microsoft key and the product repository. In order to do that, open a linux terminal window and run the following command:

It’s worth noting that you’ll only need to do these things once (per Linux machine).

Right after that, run the following command to ensure that your system is up-to-date with the latest security standards:

Last but now least, you can install the .NET SDK by typing the following command:

This last command will install the .NET Core Runtime Bundle, which includes the .NET Core runtime and the ASP.NET Core runtime. To install just the .NET Core runtime, use the following command instead:

Deploy the Web Application

Once done, we are ready to publish our .NET Core Web Application on the Linux CentOS server. The simplest thing to do that is to use a FTP client, assuming that our Linux CentOS server has a FTP server ready and working: in case it doesn’t, we can either install a FTP server from scratch by following this tutorial or use SSH or other file-transfer methods.

To publish a .NET Core Web Application via FTP you can either use the built-in FTP publishing feature or publish to a local filesystem folder and then manually upload the resulting files to your server using a standard FTP client.

How to Deploy a ASP.NET Core 2 Web Application to Linux CentOS - Tutorial

Be sure to choose a suitable folder: as you can see in the above screenshot in this tutorial we’ll be using /www/var/regexsplitter.ryadel.com/ , therefore be sure to replace this with your folder in the next paragraph.

IMPORTANT: be sure to choose the linux-x64 runtime identifier in the publish profile settings wizard, since the publishing target is a Linux machine!

As soon as the file transfer is done, remember to setup the proper permissions to the Linux CentOS folder using chown: if you don’t do that, you’ll run into a blocking issue (see Troubleshooting paragraph below).

Here’s a typical chown command to do that, assuming that you’ll be using www-data user and the www group (the -R switch means “recursive”):

Setup the Kestrel service

The next thing to do is to create and configure the Kestrel service – our web server.

Let’s start by creating the service definition file in the /etc/systemd/system/ folder:

If you prefer, replace nano with any other editor of choice. Also, feel free to replace regexsplitter with your (lower-case) web application name or with anything that will remind you that.

In the newly-created file, put the following content:

Take a look at the highlighted lines above: these are the values that you will have to configure depending on your web application. Replace our sample values with your actual working directory, your .NET Core DLL file name and the Linux user you’d like to use (the same you’ve granted folder permissions with chown early on).

After you performed the required changes, save and close the file.

Now that we have configured the service, we just need to start it…

… and check its status:

If the status command shows that something’s off (red lines or advices), check out the Troubleshooting paragraph below. If you don’t see anything like that, it most likely means that your Kestrel web service is up and running (yay!).

However, before going further, it’s definitely advisable to check out that everything is running fine. Open a web browser on your Linux CentOS machine and check out the following URL:

NOTE: the kestrel URL and/or port may vary depending on your web application configuration: check your /Properties/launchSettings.json file for details.

If everything works well, we should be able to see your website!

If we do, we can enable the service to make it automatically start upon each boot:

In case we don’t, take a look at the browser’s error message and then check out the Troubleshooting paragraph below.

Why we’re not using Kestrel?

You could be tempted to just use the Kestrel web service and get the job done now, without having to deal with NGINX. Despite being 100% possible, we strongly advise against doing that for the same reasons stated by Microsoft here:

Kestrel is great for serving dynamic content from ASP.NET Core. However, the web serving capabilities aren’t as feature rich as servers such as IIS, Apache, or Nginx. A reverse proxy server can offload work such as serving static content, caching requests, compressing requests, and SSL termination from the HTTP server. A reverse proxy server may reside on a dedicated machine or may be deployed alongside an HTTP server.

As we can see, they are basically enumerating the advantages of the edge-origin pattern that we already introduced early on. To put it short, Kestrel is not meant to be used to the frontline: therefore, the right thing to do is definitely to keep it far from the edge and leave such task to NGINX.

Configure NGINX

The last thing we have to do is to install and configure NGINX to serve our .NET Core web application as a reverse proxy. In this paragraph we’ll just assume that you already have a NGINX instance installed on your Linux CentOS machine and that you have at least a vague idea of how it works. If you don’t, you can either read this tutorial to install NGINX and them take some time to read these posts to learn its basics.

Here’s the relevant code snippet that you can add to (or include in) your /etc/nginx/nginx.conf configuration file to setup the regexsplitter.ryadel.com domain as a reverse-proxy:

IMPORTANT: The above settings take for granted that the web application is hosted through a SSL channel (TCP port 443) with a valid SSL certificate: if that’s not the case, just remove the first server block and change the listeners from TCP 443 to TCP 80 in the second server block.

Again, replace the urls, paths and names according to your own project. Also, be sure to add or include any other configuration file or setting you want or need to use (seo, cache configuration, static content, blocked locations, anti-DDoS features and so on). We intentionally kept it to the bare minimum, yet you most likely want to have more stuff than that.

Troubleshooting

If you follow the tutorial throughly, there’s a high chance that you shouldn’t need this paragraph at all. However, in the unlikely scenario that something went wrong, here’s a brief list of the most common issues that might occur, together with some suggestions and workarounds.

217/User – Kestrel permissions error

This error message can be seen when you check the status of the Kestrel service from your Linux console:

(code=exited, status=217/USER)

Solution: check the user written in the Kestrel service file (as explained in the Setup the Kestrel service paragraph) and be sure that the permissions have been set accordingly using the sudo chown command.

SSL_ERROR_RX_RECORD_TOO_LONG

This error message can be seen when you check your Kestrel web server by visiting the http://localhost:5000 URL with your Linux server’s local browser.

Secure Connection Failed

An error occurred during a connection to localhost:5000.
SSL received a record that exceeded the maximum permissible length.

Error code: SSL_ERROR_RX_RECORD_TOO_LONG

How to Deploy a ASP.NET Core 2 Web Application to Linux CentOS - Tutorial

If you see this, it mostly means that you’ve configured your Kestrel web server to only accept SSL connections, but you didn’t setup the SSL certificate accordingly.

Solution: review your Kestrel SSL certificate configuration (if you need help with that, check out this post). Alternatively, you can consider to re-configure your web application to accept non-SSL connections and then publish it again: there will be no security issues if you do that, since the origin server will be only accessible by the reverse-proxy: the SSL channel will still be used for outside requests, as it will be handled by NGINX.

Conclusion

That’s it, at least for the time being. I sincerely hope that this tutorial will help other developers and system administrators to host their .NET Core web applications to Linux-based servers, since they work really well  – arguably even better than their Windows counterpart, at least in some scenarios!

If you want to take a close look to a .NET Core web application that is currently hosted on a Linux server with the same technique shown in above tutorial, check out regexsplitter.ryadel.com: the full source-code is also available on GitHub.

This post is part of a series of articles, tutorials and guides on the NGINX web server & reverse proxy. To read the other posts, click here!

 

About Ryan

IT Project Manager, Web Interface Architect and Lead Developer for many high-traffic web sites & services hosted in Italy and Europe. Since 2010 it's also a lead designer for many App and games for Android, iOS and Windows Phone mobile devices for a number of italian companies. Microsoft MVP for Development Technologies since 2018.

View all posts by Ryan

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.