In this article, we are going to learn how to deploy ASP.NET Core applications to an Ubuntu Linux server with Apache.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started.

Create a New ASP.NET Core Web API Project

Let’s create a new solution using the dotnet command line interface. We’ll open a command prompt window in the location where we want to create the solution and use these commands:

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
mkdir DeployingToLinuxWithApache
cd DeployingToLinuxWithApache
dotnet new sln --name DeployingToLinuxWithApache
dotnet new webapi --name DeployingToLinuxWithApache
dotnet sln add DeployingToLinuxWithApache/DeployingToLinuxWithApache.csproj

This creates a new solution and then adds a new Web API project to the solution.

Install the ASP.NET Core Runtime

Before we can run the ASP.NET Core applications on Linux using Apache, we need to first install the ASP.NET Core runtime. The runtime provides the necessary components and libraries required to run ASP.NET Core applications on Linux:

sudo apt-get update && sudo apt-get install -y aspnetcore-runtime-7.0

When this is completed, let’s run dotnet --info in the terminal to confirm that it’s been installed successfully:

dotnet --info

After we execute the command, we can inspect the output:

Host:
  Version:      7.0.8
  Architecture: x64
  Commit:       4b0550942d

.NET SDKs installed:
  No SDKs were found.

.NET runtimes installed:
  Microsoft.AspNetCore.App 7.0.8 [/usr/lib/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 7.0.8 [/usr/lib/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  DOTNET_ROOT       [/usr/lib/dotnet]

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

The output shows that the runtime is installed, specifically version 7.0.8.

Deploy the App and Run It in Kestrel

Before we deploy to the Linux server, we need to create the deployment folder and make sure it’s set to the right permissions:

mkdir -p /var/www/app
chmod 666 /var/www/app

Next, let’s publish the app to a folder on the development machine. In Visual Studio, create a new publish profile with Folder as the target and then select the folder location. After the profile has been created, let’s click Publish to create the deployment files in the specified location.

Now that we have the files on the development machine, let’s copy them to the Linux server using WinSCP:

Transfer asp.net core application to linux using WinSCP

When we install the dotnet runtime on the Linux server, it comes with Kestrel which is a lightweight web server for ASP.NET Core. We’ll use Kestrel to confirm the app runs. Let’s navigate to the deployment path and run it:

dotnet DeployingToLinuxWithApache.dll

This starts up the application and produces the output:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /var/www/app

The output shows that Kestrel has started successfully and is listening on port 5000. We can confirm that the app is running using the curl command:

curl -X 'GET' 'http://localhost:5000/WeatherForecast' -H 'accept: text/plain'

This makes a GET request to the /WeatherForecast endpoint and produces the output:

[
  {"date":"2023-07-13","temperatureC":24,"temperatureF":75,"summary":"Warm"},
  {"date":"2023-07-14","temperatureC":21,"temperatureF":69,"summary":"Sweltering"},
  {"date":"2023-07-15","temperatureC":2,"temperatureF":35,"summary":"Chilly"},
  {"date":"2023-07-16","temperatureC":46,"temperatureF":114,"summary":"Chilly"},
  {"date":"2023-07-17","temperatureC":-9,"temperatureF":16,"summary":"Warm"}
]

Now that we have it running in Kestrel, let’s set up Apache as a reverse proxy server. 

Install and Configure Apache on the Server

A reverse proxy server like Apache excels more than Kestrel at handling some web-serving capabilities such as caching and compressing requests, as well as serving static content. Let’s install it using apt:

sudo apt-get update && sudo apt-get install -y apache2

Now that it’s installed, let’s configure it to function as a reverse proxy. First, we need to enable the proxy and proxy_http modules:

sudo a2enmod proxy proxy_http

The a2enmod command enables the modules and produces the output:

Enabling module proxy.
Considering dependency proxy for proxy_http:
Module proxy already enabled
Enabling module proxy_http.
To activate the new configuration, you need to run:
  systemctl restart apache2

Next, let’s create a conf file in /etc/apache2/sites-available/ named app.conf:

nano /etc/apache2/sites-available/app.conf

The nano command creates the file if it does not exist and opens it for editing.

Then, we are going to modify the created file:

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ErrorLog ${APACHE_LOG_DIR}/app-error.log
    CustomLog ${APACHE_LOG_DIR}/app-access.log common
</VirtualHost>

The directives in the file instruct Apache to listen for requests on port 80 and forward them for handling on port 5000 of the same server, which is where our app is running in Kestrel.

After this let’s disable the default site, enable the one we just created, and then restart Apache:

sudo a2dissite 000-default
sudo a2ensite app
sudo systemctl restart apache2

When we test this using curl:

curl -X 'GET' 'http://localhost/WeatherForecast' -H 'accept: text/plain'

We can see it’s still running as expected:

[ 
  {"date":"2023-07-13","temperatureC":24,"temperatureF":75,"summary":"Warm"},
  {"date":"2023-07- 14","temperatureC":21,"temperatureF":69,"summary":"Sweltering"},
  {"date":"2023-07-15","temperatureC":2,"temperatureF":35,"summary":"Chilly"},
  {"date":"2023-07-16","temperatureC":46,"temperatureF":114,"summary":"Chilly"},
  {"date":"2023-07-17","temperatureC":-9,"temperatureF":16,"summary":"Warm"}
]

To wrap this up, let’s configure Kestrel to run as a Linux service so it will be easier to monitor and manage.

Configure the App to Run as a Service

Let’s create a systemd unit file named kestrel-app.service in the /etc/systemd/system directory:

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

Then we are going to modify the created file:

[Unit]
Description=ASP.NET Core Web API running on Ubuntu

[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/dotnet /var/www/app/DeployingToLinuxWithApache.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-web-api
# This user should exist on the server and have ownership of the deployment directory
User=deploy
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target

Let’s save the file after adding the content, and then enable and start up the service:

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

After enabling and starting the service, the last command checks the status of the service and produces the output:

● kestrel-app.service - ASP.NET Core Web App running on Ubuntu
     Loaded: loaded (/etc/systemd/system/kestrel-app.service; enabled; preset: enabled)
     Active: active (running) since Thu 2023-07-13 05:37:45 UTC; 1s ago
   Main PID: 2889 (dotnet)
      Tasks: 22 (limit: 9379)
     Memory: 18.3M
        CPU: 1.615s
     CGroup: /system.slice/kestrel-app.service
             └─2889 /usr/bin/dotnet /var/www/app/DeployingToLinuxWithApache.dll

Jul 13 05:37:45 ubuntu systemd[1]: Started ASP.NET Core Web App running on Ubuntu.
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]: info: Microsoft.Hosting.Lifetime[14]
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]:       Now listening on: http://localhost:5000
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]: info: Microsoft.Hosting.Lifetime[0]
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]:       Application started. Press Ctrl+C to shut down.
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]: info: Microsoft.Hosting.Lifetime[0]
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]:       Hosting environment: Production
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]: info: Microsoft.Hosting.Lifetime[0]
Jul 13 05:37:46 ubuntu dotnet-web-app[2889]:       Content root path: /var/www/app

With this, our deployment is complete and we can use the previous curl command to confirm that everything still works as expected.

If you want to learn more about deploying with Nginx as the reverse proxy server, you can read Deploy ASP.NET Core on Linux with Nginx .

Conclusion

In this article, we have deployed an ASP.NET Core Web API to Ubuntu with Apache as a reverse proxy server.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!