Chad Krause

A small blog about my projects

Deploy .Net Core 5.0 API to Apache on CentOS

How to deploy a .NET 5.0 API app on Linux

In this blog post, we will look at everything you need to do to deploy a .NET 5.0 app on CentOS 7. This will also work with .NET Core 3.1 and I'm sure other versions. I have CPanel on my server, but I will basically be bypassing that. I am also running an Angular (11? 12? Not sure what version, they keep updating. By the time you read this, 4 more versions will probably have been released)

You will need root access

Let's take a look at the steps:

  1. (Optional) Create DNS records
  2. Publish linux-compatible executable
  3. Copy over files to CentOS system
  4. Install .NET Runtime
  5. Create a service (daemon?) to monitor and keep app running
  6. Create a reverse proxy so Apache can direct requests to the app
  7. Restart Apache and test
  8. (Optional) Updating the api

1. Create DNS record(s) for domain

I have my API running on a subdomain (, and my front end (Angular) running on another subdomain (

I use CPanel to manage all of my DNS stuff because I honestly just don't want to deal with stuff myself.

It's easy in CPanel. Just go to the main CPanel page, then under Domains, click on Subdomains and add the two subdomains you want. Mine's not actually running on and, I just don't want to give out all my personal site info.

2. Publish linux-compatible executable

This one is very easy, when you publish the project, just make sure the Target Runtime is linux-x64.

Screenshot 2021-04-17 220844.png

I like to publish my files somewhere easy to access. I publish to C:/publish/{project-name}-{date}/

3. Copy over files to CentOS system

I use CyberDuck and FTP the files over. I keep the API files out of the public_html folder. I actually just have it in the another folder in the same directory as public_html folder. For this example, I put it in the net-api folder. I suggest not putting spaces in folder names. It makes it easier to deal with later

Screenshot 2021-04-17 221505.png

4. Install .NET Runtime

Now you have to install the runtime. Similar to how you have to do it the first time when deploying to an IIS server. It's easy to just follow these steps

But if you want the easist way, you just execute these two commands in on the server via SSH. I assume you have SSH open by now, btw.

  1. Add the Microsoft repository
sudo rpm -Uvh
  1. Install the .NET 5.0 Runtime
sudo yum install dotnet-runtime-5.0

And follow the prompts it gives you

5. Create a service (daemon?) to monitor and keep app running

I'm not sure if it's a daemon or service or what, but here's how you have the system run the app.

Create a new service file in the right directory

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

BTW, the app is running on a kestrel server. You basically forward requests to that kestrel server from apache.

In that file, add this content: (copied right from

Description=Example .NET Web API App running on Ubuntu

ExecStart=/usr/bin/dotnet /var/www/net-api/app.dll
# Restart service after 10 seconds if the dotnet service crashes:


You'll want to modify Description, WorkingDirectory, ExecStart (the executable .dll), SyslogIdentifier, and User

After you save that file, execute these two commands:

sudo systemctl start kestrel-app.service
sudo systemctl status kestrel-app.service

That will install and check the status of the server. The status part is optional, but it verifies it's working and will give you the last few lines of the log

6. Create a reverse proxy so Apache can direct requests to the app

This was the hardest part for me. Basically here's the case:

  • The requests go to the Apache server first (which is managed by Cpanel for me). That is normal and it should stay like that
  • Your .NET 5.0 API is running internally on the a Kestrel server on your on port 5000
  • A reverse proxy takes the requests going to apache, and directs them to the Kestrel (.NET 5.0) app

Okay, so now that you know that, you have to create a Virtual Host to set up that reverse proxy

For me, since mine is managed by CPanel, I put mine in a specific location, then rebuild the apache configuration. The rebuild takes my configuration and automatically builds the correct with it

Here's the location I used (since I use CPanel, I used this guide

sudo nano /etc/apache2/conf.d/userdata/ssl/2_4/kestrelapp.conf

In that file, put this:

SSLProxyEngine on
ProxyPass /
ProxyPassReverse /
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /(.*) ws://$1 [P]

Also, this configuration file will also handle websockets (SignalR)

After you save that file, execute these two commands:


The first will rebuild the real configuration that Apache uses, the second will restart the Apache server so the changes can take place

7. Restart Apache and test

You already restarted Apache by executing the last command. So now you can check your api to see if it works. If it doesn't work, make sure your kestrelapp.conf is correct. If not, your server support may be able to help

8. (Optional) Updating the api

To update the api, all you need to do is

  1. Stop the service
sudo systemctl stop kestrel-app.service
  1. Copy over the new files
  2. Start the service again
sudo systemctl start kestrel-app.service

That should work for you.


Everyone's setup is different, so it's hard to give specific troubleshooting advice

If it's not working, make sure that the service is running without issue. You can run it manually by executing the following command in the folder where you have your app:

dotnet api.dll

Where api.dll is your api's executable

If you're getting a 503 error, make sure your configuration is correct. The app will tell you where it's listening for requests when it first starts. You can find that by running these two commands

Which will give you the following output:

● kestrel-api.service - Chad Krause's .NET Api (
   Loaded: loaded (/etc/systemd/system/kestrel-api.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2021-04-17 22:48:13 EDT; 8s ago
 Main PID: 31438 (dotnet)
   CGroup: /system.slice/kestrel-api.service
           └─31438 /usr/bin/dotnet /home/username/api/ChadKrause.Api.dll

Apr 17 22:48:13 systemd[1]: Started Chad Krause's .NET Api (
Apr 17 22:48:14 dotnet-example[31438]: 2021-04-17 22:48:14.3569 |DEBUG |ChadKrause.Api.Program |init main |
Apr 17 22:48:14 dotnet-example[31438]: 2021-04-17 22:48:14.8034 |INFO |Microsoft.Hosting.Lifetime |Now listening on: http://localhost:5000 |
Apr 17 22:48:14 dotnet-example[31438]: 2021-04-17 22:48:14.8124 |INFO |Microsoft.Hosting.Lifetime |Application started. Press Ctrl+C to shut down. |
Apr 17 22:48:14 dotnet-example[31438]: 2021-04-17 22:48:14.8124 |INFO |Microsoft.Hosting.Lifetime |Hosting environment: Production |
Apr 17 22:48:14 dotnet-example[31438]: 2021-04-17 22:48:14.8136 |INFO |Microsoft.Hosting.Lifetime |Content root path: /home/username/api |

You can see that it's listening to stuff on http://localhost:5000


Let me know how this works for you!

Comments are appreciated but rarely ever looked at

All comments

Apr 17, 2021