DEV Community

Cover image for How to run ASP.NET Core as a service on Linux (RHEL)
Niels Swimburger.NET ๐Ÿ”
Niels Swimburger.NET ๐Ÿ”

Posted on • Originally published at swimburger.net on

How to run ASP.NET Core as a service on Linux (RHEL)

This article walks us through running a ASP.NET Core web application on Linux (RHEL) using systemd. Here's what we'll cover:

  1. Running ASP.NET Core using systemd
  2. Adding Systemd integration package
  3. Making ASP.NET Core accessible externally (Kestrel only, no reverse proxy)
  4. Serving ASP.NET Core over port 80 & 443 (Kestrel only, no reverse proxy)

The end goal is to serve ASP.NET Core directly via the built-in Kestrel webserver over port 80/443.

To learn how to run .NET Core services (non web stuff) on Linux, check out "How to run a .NET Core as a service using Systemd on Linux"

Prerequisites:

This walkthrough should work for most .NET Core supported Linux distributions, not just RHEL.

Run ASP.NET Core using Systemd

Let's start by creating a new ASP.NET Core application using the web-template:

mkdir ~/AspNetSite
cd ~/AspNetSite
dotnet new web
Enter fullscreen mode Exit fullscreen mode

We'll be using this application throughout the walkthrough. Let's verify that the web application works:

dotnet run
# Output should looks like this:
#   info: Microsoft.Hosting.Lifetime[0]
#         Now listening on: https://localhost:5001
#   info: Microsoft.Hosting.Lifetime[0]
#         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: Development
#   info: Microsoft.Hosting.Lifetime[0]
#         Content root path: /home/yourusername/AspNetSite
Enter fullscreen mode Exit fullscreen mode

Open a separate shell (leave the other shell running) and use the curl HTTP-client to send an HTTP request to the application:

# while 'dotnet run' is running, open a new shell to run this curl command
curl http://localhost:5000
# Output should be 'Hello World!'
Enter fullscreen mode Exit fullscreen mode

If the application works, we can publish it somewhere logical such as '/srv/AspNetSite':

sudo mkdir /srv/AspNetSite
sudo chown yourusername /srv/AspNetSite/
dotnet publish -c Release -o /srv/AspNetSite/
Enter fullscreen mode Exit fullscreen mode

The published result contains an executable called 'AspNetSite' which will run the application. Let's verify we can also run the published application:

cd /srv/AspNetSite/
./AspNetSite
# Output should looks like this:
#   info: Microsoft.Hosting.Lifetime[0]
#         Now listening on: http://localhost:5000
#   info: Microsoft.Hosting.Lifetime[0]
#         Now listening on: https://localhost:5001
#   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: /srv/AspNetSite
Enter fullscreen mode Exit fullscreen mode

Make sure to return to the original directory by running cd ~/AspNetSite. To run services on Linux, Systemd uses ' service unit configuration' files to describe how to run services. Let's create the file ' AspNetSite.service' inside our project so we can store it in source control along with our code. Add the following content to 'AspNetSite.service':

[Unit]
Description=ASP.NET Core web template

[Service]
# will set the Current Working Directory (CWD)
WorkingDirectory=/srv/AspNetSite
# systemd will run this executable to start the service
ExecStart=/srv/AspNetSite/AspNetSite
# to query logs using journalctl, set a logical name here  
SyslogIdentifier=AspNetSite

# Use your username to keep things simple, for production scenario's I recommend a dedicated user/group.
# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app.
# To update permissions, use 'chown yourusername -R /srv/AspNetSite' to take ownership of the folder and files,
#       Use 'chmod +x /srv/AspNetSite/AspNetSite' to allow execution of the executable file.
User=yourusername

# ensure the service restarts after crashing
Restart=always
# amount of time to wait before restarting the service              
RestartSec=5

# copied from dotnet documentation at
# https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1#code-try-7
KillSignal=SIGINT
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

# This environment variable is necessary when dotnet isn't loaded for the specified user.
# To figure out this value, run 'env | grep DOTNET_ROOT' when dotnet has been loaded into your shell.
Environment=DOTNET_ROOT=/opt/rh/rh-dotnet31/root/usr/lib64/dotnet

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Make sure to update the 'User' to your username. Refer to the comments for an explanation of the specified options. For more information on the service unit configuration file, read the freedesktop manual page or the RedHat documentation.

Systemd expects all configuration files to be put under '/etc/systemd/system/'. Copy the service configuration file to '/etc/systemd/system/AspNetSite.service' and tell systemd to reload the configuration files.

sudo cp AspNetSite.service /etc/systemd/system/AspNetSite.service
sudo systemctl daemon-reload
Enter fullscreen mode Exit fullscreen mode

Now systemd is aware of the new 'AspNetSite' service. Using systemctl start AspNetSite we can start the service.

Using systemctl status AspNetSite we can query the status of the service. Let's start the service and check its status:

sudo systemctl start AspNetSite
sudo systemctl status AspNetSite
# Output should be similar to below:
#   โ— AspNetSite.service - ASP.NET Core web template
#      Loaded: loaded (/etc/systemd/system/AspNetSite.service; enabled; vendor preset: disabled)
#      Active: active (running) since Wed 2020-01-29 17:06:24 UTC; 13s ago
#    Main PID: 5187 (AspNetSite)
#      CGroup: /system.slice/AspNetSite.service
#              โ””โ”€5187 /srv/AspNetSite/AspNetSite
#   
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: Now listening on: http://localhost:5000
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: Now listening on: https://localhost:5001
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: Application started. Press Ctrl+C to shut down.
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: Hosting environment: Production
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:06:25 rhtest AspNetSite[5187]: Content root path: /srv/AspNetSite
Enter fullscreen mode Exit fullscreen mode

Due to the Restart=always option, systemd will restart our service in case it crashed. But it will not automatically start the service when the machine reboots. To enable automatic startup, use the following command:

sudo systemctl enable AspNetSite
Enter fullscreen mode Exit fullscreen mode

If everything is working correctly, we should be able to curl the application via localhost:5000:

curl http://localhost:5000
# Output should be 'Hello World!'
Enter fullscreen mode Exit fullscreen mode

The website is now running as a systemd service. There's a systemd-package provided by Microsoft to improve the integration with systemd. Let's set that up next.

Add Systemd integration package

Microsoft recently added a package to better integrate with systemd. When the integration is installed, the application will notify systemd when it's ready and when it's stopping. Additionally, systemd will understand the different log levels that the application logs.

Using the dotnet CLI, add the 'Microsoft.Extensions.Hosting.Systemd' (nuget) package:

dotnet add package Microsoft.Extensions.Hosting.Systemd --version 3.1.1
Enter fullscreen mode Exit fullscreen mode

Next, we'll need to add one line to the 'Program.cs', .UseSystemd():

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetSite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSystemd()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
Enter fullscreen mode Exit fullscreen mode

For demonstration purposes of the logging integration, update the 'Program.cs' file with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetSite
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    logger.LogInformation("Information - Hello World");
                    logger.LogWarning("Warning - Hello World");
                    logger.LogError("Error - Hello World");
                    logger.LogCritical("Critical - Hello World");
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Lastly, we need to update the file 'AspNetSite.service' to specify 'type=Notify':

[Unit]
Description=ASP.NET Core web template

[Service]
Type=notify
# will set the Current Working Directory (CWD)
WorkingDirectory=/srv/AspNetSite
# systemd will run this executable to start the service
ExecStart=/srv/AspNetSite/AspNetSite
# to query logs using journalctl, set a logical name here
SyslogIdentifier=AspNetSite

# Use your username to keep things simple, for production scenario's I recommend a dedicated user/group.
# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app.
# To update permissions, use 'chown yourusername -R /srv/AspNetSite' to take ownership of the folder and files,
#       Use 'chmod +x /srv/AspNetSite/AspNetSite' to allow execution of the executable file.
User=yourusername

# ensure the service restarts after crashing
Restart=always
# amount of time to wait before restarting the service
RestartSec=5

# copied from dotnet documentation at
# https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1#code-try-7
KillSignal=SIGINT
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

# This environment variable is necessary when dotnet isn't loaded for the specified user.
# To figure out this value, run 'env | grep DOTNET_ROOT' when dotnet has been loaded into your shell.
Environment=DOTNET_ROOT=/opt/rh/rh-dotnet31/root/usr/lib64/dotnet

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Let's deploy all our changes. We'll need to publish the .NET app and stop/reload/start the systemd service:

sudo systemctl stop AspNetSite
dotnet publish -c Release -o /srv/AspNetSite/
sudo cp AspNetSite.service /etc/systemd/system/AspNetSite.service
sudo systemctl daemon-reload
sudo systemctl start AspNetSite
Enter fullscreen mode Exit fullscreen mode

The application logs are being captured by systemd. We can query the logs using 'journalctl', here are some examples:

sudo journalctl -u AspNetSite #query all output, oldest to newest
sudo journalctl -u AspNetSite -f #query all output and follow live
sudo journalctl -u AspNetSite -r #query all output, newest to oldest
sudo journalctl -u AspNetSite --since="2020-01-17 11:00:00" --until="2020-01-17 11:15:00" #filter by time
Enter fullscreen mode Exit fullscreen mode

The unit-flag (-u) allows us to filter by 'SyslogIdentifier' which we specified in 'AspNetSite.service'.

We can verify that the .NET Core logging integrates correctly by using the priority-flag (-p) on 'journalctl'. This will filter the output according the log levels below:

LogLevel Syslog level systemd name
Trace/Debug 7 debug
Information 6 info
Warning 4 warning
Error 3 err
Critical 2 crit

For example, the following command will only print output with log level 4 and below meaning warning, error, and critical:

sudo journalctl -u AspNetSite -p 4
# Output should be empty
Enter fullscreen mode Exit fullscreen mode

Let's first make a couple of HTTP request to the application using curl and then run the 'journalctl' query:

curl http://localhost:5000
curl http://localhost:5000
curl http://localhost:5000
sudo journalctl -u AspNetSite -p 4
Enter fullscreen mode Exit fullscreen mode

The 'journalctl' command should now return the different log statements we wrote 3 times.

Log output from journalctl-command

The 'UseSystemd' function will not do anything when run outside of a systemd service. The implementation checks if the OS is a Unix system and whether the parent process is systemd.

If not, the systemd integration is skipped.

We now have our systemd-integration ready, but the application is still not accessible outside of the machine. Let's make the application accessible externally.

Make ASP.NET Core accessible externally

As demonstrated below, the application is only accessible via localhost on the machine and not via the machine's IP-address.

curl http://localhost:5000
# Output should be 'Hello World!'

# try curl'ing using your machine's IP, to list IP's on RHEL use 'ip addr show'
curl http://10.0.0.4:5000
# Output should be 'curl: (7) Failed to connect to 10.0.0.4 port 5000: Connection refused'
Enter fullscreen mode Exit fullscreen mode

Out of the box, the application is configured to listen to http://localhost:5000 & https://localhost:5001. This works great for development, but we want to expose our application to other machines in the network or even to the internet. In ASP.NET Core there are many ways to configure the URL's. We can configure it through code, appsettings.json, environment variables, or command line arguments.

Let's go with environment variables. Add the 'ASPNETCORE_URLS' environment variable to the 'AspNetSite.service' file:

[Unit]
Description=ASP.NET Core web template

[Service]
Type=notify
# will set the Current Working Directory (CWD)
WorkingDirectory=/srv/AspNetSite
# systemd will run this executable to start the service
ExecStart=/srv/AspNetSite/AspNetSite
# to query logs using journalctl, set a logical name here  
SyslogIdentifier=AspNetSite

# Use your username to keep things simple, for production scenario's I recommend a dedicated user/group.
# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app.
# To update permissions, use 'chown yourusername -R /srv/AspNetSite' to take ownership of the folder and files,
#       Use 'chmod +x /srv/AspNetSite/AspNetSite' to allow execution of the executable file.
User=yourusername

# ensure the service restarts after crashing
Restart=always
# amount of time to wait before restarting the service              
RestartSec=5

# copied from dotnet documentation at
# https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1#code-try-7
KillSignal=SIGINT
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

# This environment variable is necessary when dotnet isn't loaded for the specified user.
# To figure out this value, run 'env | grep DOTNET_ROOT' when dotnet has been loaded into your shell.
Environment=DOTNET_ROOT=/opt/rh/rh-dotnet31/root/usr/lib64/dotnet

# When using the out of the box ASP.NET Tempates, this environment variable will allow you to override 
# which IP & ports the Kestrel Web Server will listen to. 
# Using the * as a wildcard will listen to any IP, localhost and other IP's the machine may have
Environment=ASPNETCORE_URLS=http://*:5000;https://*:5001


[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Instead of specifying localhost or an IP-address, the asterisk (*) will act as a wildcard. The application will now listen to localhost and all IP-addresses assigned to the machine.

Let's copy the updated configuration file and reload/restart the systemd service:

sudo cp AspNetSite.service /etc/systemd/system/AspNetSite.service
sudo systemctl daemon-reload
sudo systemctl restart AspNetSite
sudo systemctl status AspNetSite
# Output should be similar to below:
#   โ— AspNetSite.service - ASP.NET Core web template
#      Loaded: loaded (/etc/systemd/system/AspNetSite.service; enabled; vendor preset: disabled)
#      Active: active (running) since Wed 2020-01-29 17:17:48 UTC; 5s ago
#    Main PID: 5937 (AspNetSite)
#      CGroup: /system.slice/AspNetSite.service
#              โ””โ”€5937 /srv/AspNetSite/AspNetSite
#   
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Now listening on: http://[::]:5000
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Now listening on: https://[::]:5001
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Application started. Press Ctrl+C to shut down.
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Hosting environment: Production
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Content root path: /srv/AspNetSite
Enter fullscreen mode Exit fullscreen mode

Instead of http://localhost:5000, we can now see http://[::]:5000. Now that the application is bound to the machine's IP-address, we should be able to curl it via IP from within the machine:

# try curl'ing using your machine's IP, to list IP's on RHEL use 'ip addr show'
curl http://10.0.0.4:5000
# Output should be 'Hello World!'
Enter fullscreen mode Exit fullscreen mode

Does this mean the website is accessible from outside the machine now?

Almost, Red Hat comes with a built-in firewall which will block the traffic. Using the 'firewall-cmd' utility, we can update the firewall configuration to allow TCP traffic over port 5000 & 5001:

sudo firewall-cmd --zone=public --add-port 5000/tcp --permanent
sudo firewall-cmd --zone=public --add-port 5001/tcp --permanent
sudo firewall-cmd --reload
Enter fullscreen mode Exit fullscreen mode

Now the website will be accessible from other machines within the network.

In case you're running this RHEL machine in the cloud, you will also have to ensure whatever security is provided by the cloud also allow TCP over port 5000 & 5001.

Once that's done, the website should be accessible to the internet.

Serve ASP.NET Core over port 80 & 443

By default, Linux machines won't allow processes to use well known ports (ports lower than 1024).

If we try to run the application using port 80 and/or 443, we'll get a permission error:

/srv/AspNetSite/AspNetSite --urls "http://*:80;https://*:443"
# Output:
#   crit: Microsoft.AspNetCore.Server.Kestrel[0]
#         Unable to start Kestrel.
#   System.Net.Sockets.SocketException (13): Permission denied
#   ...
Enter fullscreen mode Exit fullscreen mode

There are many ways to work around this restriction.

Use a Reverse Proxy

We can setup a reverse proxy to listen to port 80 & 443 and have it forward traffic to the ASP.NET Core application. This process is well documented by Microsoft:

This is a great option for many reasons, but we're not going to do this since our goal for this walkthrough is to stick to the built-in Kestrel server exclusively.

Grant CAP_NET_BIND_SERVICE capability

Using the following command, we can give the AspNetSite executable the 'CAP_NET_BIND_SERVICE' capability. This capability will allow the process to bind to well known ports.

sudo setcap CAP_NET_BIND_SERVICE=+eip /srv/AspNetSite/AspNetSite
/srv/AspNetSite/AspNetSite --urls "http://*:80;https://*:443"
# Output:
#   info: Microsoft.Hosting.Lifetime[0]
#         Now listening on: http://[::]:80
#   info: Microsoft.Hosting.Lifetime[0]
#         Now listening on: https://[::]:443
#   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: /home/yourusername/AspNetSite
Enter fullscreen mode Exit fullscreen mode

Every time the executable is updated the 'CAP_NET_BIND_SERVICE' capability will be lost. We could make this command as part of a deployment script, but the systemd service unit configuration files has an option called 'AmbientCapabilities'.

When configuring this option to 'CAP_NET_BIND_SERVICE', systemd will grant the capability to the service for us. Let's update the 'AspNetSite.service' file to update the ports and add the capability to bind to well known ports.

[Unit]
Description=ASP.NET Core web template

[Service]
Type=notify
# will set the Current Working Directory (CWD)
WorkingDirectory=/srv/AspNetSite
# systemd will run this executable to start the service
ExecStart=/srv/AspNetSite/AspNetSite
# to query logs using journalctl, set a logical name here
SyslogIdentifier=AspNetSite

# Use your username to keep things simple, for production scenario's I recommend a dedicated user/group.
# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app.
# To update permissions, use 'chown yourusername -R /srv/AspNetSite' to take ownership of the folder and files,
#       Use 'chmod +x /srv/AspNetSite/AspNetSite' to allow execution of the executable file.
User=yourusername

# ensure the service restarts after crashing
Restart=always
# amount of time to wait before restarting the service
RestartSec=5

# copied from dotnet documentation at
# https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1#code-try-7
KillSignal=SIGINT
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

# This environment variable is necessary when dotnet isn't loaded for the specified user.
# To figure out this value, run 'env | grep DOTNET_ROOT' when dotnet has been loaded into your shell.
Environment=DOTNET_ROOT=/opt/rh/rh-dotnet31/root/usr/lib64/dotnet

# When using the out of the box ASP.NET Tempates, this environment variable will allow you to override
# which IP & ports the Kestrel Web Server will listen to
Environment=ASPNETCORE_URLS=http://*:80;https://*:443

# give the executed process the CAP_NET_BIND_SERVICE capability. This capability allows the process to bind to well known ports.
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

For the last time, copy the 'AspNetSite.service' file and reload/restart the AspNetSite service.

sudo cp AspNetSite.service /etc/systemd/system/AspNetSite.service
sudo systemctl daemon-reload
sudo systemctl restart AspNetSite
sudo systemctl status AspNetSite
# Output should be similar to below:
#   โ— AspNetSite.service - ASP.NET Core web template
#      Loaded: loaded (/etc/systemd/system/AspNetSite.service; enabled; vendor preset: disabled)
#      Active: active (running) since Wed 2020-01-29 17:17:48 UTC; 5s ago
#    Main PID: 5937 (AspNetSite)
#      CGroup: /system.slice/AspNetSite.service
#              โ””โ”€5937 /srv/AspNetSite/AspNetSite
#   
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Now listening on: http://[::]:80
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Now listening on: https://[::]:443
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Application started. Press Ctrl+C to shut down.
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Hosting environment: Production
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: info: Microsoft.Hosting.Lifetime[0]
#   Jan 29 17:17:48 rhtest AspNetSite[5937]: Content root path: /srv/AspNetSite
Enter fullscreen mode Exit fullscreen mode

The web application is now listening to port 80 & 443, but the built-in firewall will still block traffic coming in over those ports. Update the built-in firewall and any other network security to allow traffic over port 80 & 443:

sudo firewall-cmd --zone=public --add-port 80/tcp --permanent
sudo firewall-cmd --zone=public --add-port 443/tcp --permanent
sudo firewall-cmd --reload
Enter fullscreen mode Exit fullscreen mode

Visiting the website over port 80 using the browser should now return "Hello World!".

Hello World! message coming from the webserver in the browser

Summary

We now have a public facing ASP.NET Core application served by the built-in Kestrel web server by taking the following steps:

  • deploy ASP.NET Core to RHEL under /srv/AspNetSite
  • configure systemd to run the application as a service
  • add systemd .NET Core integration to the application
  • configure the application to listen to all IP's and different ports using environment variables
  • update the built-in firewall to allow TCP traffic over 5000, 5001, 80, and 443
  • grant 'CAP_NET_BIND_SERVICE' capability to the service to allow the application to bind to well known ports such as 80 & 443

Top comments (0)