---
title: "How to run a .NET Core console app as a service using Systemd on Linux (RHEL)"
excerpt: "Let's learn how to run a .NET Core console application on systemd. After running a console app as a service, we'll upgrade to using the dotnet core worker service template designed for long running services/daemons. Lastly, we'll add the systemd package integration."
publishDate: "2020-01-31T00:00:00"
createDate: "2020-01-23T06:34:35.71"
updateDate: "2022-03-21T04:56:56.133"
category: "dotnet"
categoryName: ".NET"
tags: ["DotNet","C#","CSharp","DotNetCore","RedHat","Linux",".NET",".NET Core","RHEL","Red Hat"]
permalink: "/blog/dotnet/how-to-run-a-dotnet-core-console-app-as-a-service-using-systemd-on-linux"
canonical: "/blog/dotnet/how-to-run-a-dotnet-core-console-app-as-a-service-using-systemd-on-linux"
image: {"src":"/media/n0sg5l05/dotnet_redhat-long.png","focalPoint":{"left":0.5,"top":0.49912536443148686},"crops":[{"alias":"Banner","width":1120,"height":400,"coordinates":null}]}
imageAlt: ".NET Core logo + Dotnet bot wearing Red Hat"
imageBackgroundColor: "#2D2D2D"
thumbnailImage: {"src":"/media/hlph3suu/dotnet_redhat-square.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"thumbnail","width":250,"height":250,"coordinates":null}]}
thumbnailImageAlt: ".NET Core logo + Dotnet bot wearing Red Hat"
metaTitle: "Run .NET Core console app as Systemd service on Linux"
metaDescription: "Let's learn how to run a .NET Core console application on systemd. After running a console app as a service, we'll upgrade to using the dotnet core worker service template designed for long running services/daemons. Lastly, we'll add the systemd package integration."
ogTitle: "How to run a .NET Core console app as a service using Systemd on Linux (RHEL)"
ogDescription: "Let's learn how to run a .NET Core console application on systemd. After running a console app as a service, we'll upgrade to using the dotnet core worker service template designed for long running services/daemons. Lastly, we'll add the systemd package integration."
ogImage: {"src":"/media/pihn1sda/dotnet_redhat-social.png","focalPoint":{"left":0.5,"top":0.49827507958119505},"crops":[{"alias":"OpenGraph","width":1200,"height":630,"coordinates":null}]}
ogImageAlt: ".NET Core logo + Dotnet bot wearing Red Hat"
relatedPostKeys: ["0da0d49da8fd41aaad1f4a7accdffe68","42eab58704ef42129ca5d87053f026db"]
noIndex: false
noFollow: false
sitemapExclude: false
source: {"nodeId":1257,"contentKey":"ca698c8c39f64a7abf52120d2c688e22","sortOrder":36}
---

<p class="panel panel--info">This article has been updated for .NET 6 and RHEL 8 on 03/20/2022.</p>




This article walks us through running a .NET Core console application on systemd. After running a simple console app as a service, we'll upgrade to the worker template which is designed for long running services/daemons. Lastly, we'll add the systemd package for increased integration with systemd.




<p class="panel panel--warning">To learn how to run <strong>ASP.NET Core</strong> services (web stuff) on Linux, check out "<a href="/blog/dotnet/how-to-run-aspnet-core-as-a-service-on-linux" title="How to run ASPNET Core as a service on Linux">How to run ASP.NET Core as a service on Linux without reverse proxy, no NGINX or Apache</a>".</p>

Prerequisites:


-   Red Hat Enterprise Linux (or a compatible Unix based OS)
-   .NET 6 installed (<a rel="noopener" href="https://access.redhat.com/documentation/en-us/net_core/3.1/html-single/getting_started_guide/index" target="_blank">Get started instructions from Red Hat</a>)
-   Sudo privileges




<p class="panel panel--info">This walkthrough should work for most .NET Core supported Linux distributions, not just RHEL.</p>



<p class="panel panel--info">This walkthrough should work for older versions of .NET too, but you'll need to make small modifications.</p>



## .NET Core console application {#net-core-console-application}

Let's start by making a new console application using the dotnet CLI:

<pre style="color:#000000;background:#ffffff;">mkdir ~<span style="color:#40015a; ">/HelloWorld</span>&#10;<span style="color:#bb7977; font-weight:bold; ">cd</span> ~<span style="color:#40015a; ">/HelloWorld</span>&#10;dotnet new console&#10;</pre>




Verify that the console app works by running `dotnet run`. The output should be "Hello World!".<br>If the application works, we can publish it somewhere logical such as */srv/HelloWorld*:




<pre style="color:#000000;background:#ffffff;">sudo mkdir <span style="color:#40015a; ">/srv/HelloWorld</span>               <span style="color:#696969; "># Create directory /srv/HelloWorld</span>&#10;sudo chown yourusername <span style="color:#40015a; ">/srv/HelloWorld</span>  <span style="color:#696969; "># Assign ownership to yourself of the directory</span>&#10;dotnet publish <span style="color:#44aadd; ">-c</span> Release <span style="color:#44aadd; ">-o</span> <span style="color:#40015a; ">/srv/HelloWorld</span>&#10;</pre>

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

<pre style="color:#000000;background:#ffffff;"><span style="color:#40015a; ">/srv/HelloWorld/HelloWorld</span>&#10;<span style="color:#696969; ">#output will be 'Hello World!'</span>&#10;</pre>




To run services on Linux, Systemd uses '<strong>service unit configuration</strong>' files to configure services.<br>Let's create the file <strong>'HelloWorld.service'</strong> inside our project so we can store it in source control along with our code. Add the following content to the file:




<pre style="color:#000000;background:#ffffff;">[Unit]&#10;Description=Hello World console application&#10;&#10;[Service]&#10;# systemd will run this executable to start the service&#10;# if /usr/bin/dotnet doesn<span style="color:#2a00ff; ">'</span>t work, use `which dotnet` to find correct dotnet executable path&#10;ExecStart=/usr/bin/dotnet /srv/HelloWorld/HelloWorld.dll&#10;# to query logs using journalctl, set a logical name here&#10;SyslogIdentifier=HelloWorld&#10;&#10;# Use your username to keep things simple.&#10;# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app&#10;# To update permissions, use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chown yourusername -R /srv/HelloWorld</span><span style="color:#2a00ff; ">'</span> to take ownership of the folder and files,&#10;#       Use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chmod +x /srv/HelloWorld/HelloWorld</span><span style="color:#2a00ff; ">'</span> to allow execution of the executable file&#10;User=yourusername&#10;&#10;# This environment variable is necessary when dotnet isn<span style="color:#2a00ff; ">'</span>t loaded for the specified user.&#10;# To figure out this value, run <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">env | grep DOTNET_ROOT</span><span style="color:#2a00ff; ">'</span> when dotnet has been loaded into your shell.&#10;Environment=DOTNET_ROOT=/usr/lib64/dotnet&#10;&#10;[Install]&#10;WantedBy=multi-user.target&#10;</pre>




<strong>Make sure to update the 'User' to your username.</strong> Refer to the comments in the file for a basic explanation. For more in-depth information, read the <a rel="noopener" href="https://www.freedesktop.org/software/systemd/man/systemd.service.html" target="_blank">freedesktop manual page</a> or the <a rel="noopener" href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/chap-managing_services_with_systemd" target="_blank">Red Hat documentation</a>.




<p class="panel panel--warning">Depending on how you installed .NET, the path to the dotnet executable (<em>/usr/bin/dotnet</em>) may be different. You can use the command `which dotnet` to find the correct path.<br>Alternatively, you can use `dotnet --info` which will list information about the .NET installation, and dig through the files to find the correct `dotnet` executable path.</p>




Systemd expects all configuration files to be put under <strong>'/etc/systemd/system/'</strong>. Copy the service configuration file to '<strong>/etc/systemd/system/HelloWorld.service</strong>'. Then tell systemd to reload the configuration files, and start the service.




<pre style="color:#000000;background:#ffffff;">sudo cp HelloWorld<span style="color:#800000; font-weight:bold; ">.</span>service <span style="color:#40015a; ">/etc/systemd/system/HelloWorld.service</span>&#10;sudo systemctl daemon-reload&#10;sudo systemctl start HelloWorld&#10;</pre>



<p>Using the `<code>systemctl status</code>` command we can view the status of the service:</p>



<pre style="color:#000000;background:#ffffff;">sudo systemctl status HelloWorld&#10;<span style="color:#696969; "># Output should be similar to below:</span>&#10;<span style="color:#696969; ">#    ● HelloWorld.service - Hello World console application</span>&#10;<span style="color:#696969; ">#    Loaded: loaded (/etc/systemd/system/HelloWorld.service; disabled; vendor preset: disabled)</span>&#10;<span style="color:#696969; ">#    Active: inactive (dead)</span>&#10;<span style="color:#696969; ">#</span>&#10;<span style="color:#696969; ">#    Jan 27 19:15:47 rhtest systemd[1]: Started Hello World console application.</span>&#10;<span style="color:#696969; ">#    Jan 27 19:15:47 rhtest HelloWorld[4074]: Hello World!</span>&#10;</pre>




In addition to the status command, we can use the 'journalctl' command to read everything our service is printing to the console. **Using the unit-flag (-u), we can filter down to our HelloWorld service**.




<pre style="color:#000000;background:#ffffff;">sudo journalctl <span style="color:#44aadd; ">-u</span> HelloWorld&#10;<span style="color:#696969; "># Output should be similar to below:</span>&#10;<span style="color:#696969; ">#   Jan 27 19:15:47 rhtest systemd[1]: Started Hello World console application.</span>&#10;<span style="color:#696969; ">#   Jan 27 19:15:47 rhtest HelloWorld[4074]: Hello World! </span>&#10;</pre>

The console app only logs "Hello world!" to the console and then exits. When querying the status, systemd reports the service is inactive (dead). That's because the console app starts, runs, and immediately exits. That's not very useful, so let's add some code that will let the app run until told to stop. Update **Program.cs** with the following content:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; font-weight:bold; ">while</span>(<span style="color:#7f0055; font-weight:bold; ">true</span>){&#10;	Console.WriteLine(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Hello World!</span><span style="color:#2a00ff; ">"</span>);&#10;	await Task.Delay(500);&#10;}&#10;</pre>

Let's publish the app again:

<pre style="color:#000000;background:#ffffff;">sudo systemctl stop HelloWorld <span style="color:#696969; "># stop the HelloWorld service to remove any file-locks</span>&#10;dotnet publish <span style="color:#44aadd; ">-c</span> Release <span style="color:#44aadd; ">-o</span> <span style="color:#40015a; ">/srv/HelloWorld</span>&#10;sudo systemctl start HelloWorld&#10;</pre>




Now we have a minimal application that is continuously running until told to stop.<br>If the application stops due to a crash, systemd will not automatically restart the service unless we configure that. **Add the 'Restart' & 'RestartSec' options to HelloWorld.service**:




<pre style="color:#000000;background:#ffffff;">[Unit]&#10;Description=Hello World console application&#10;&#10;[Service]&#10;# systemd will run this executable to start the service&#10;# if /usr/bin/dotnet doesn<span style="color:#2a00ff; ">'</span>t work, use `which dotnet` to find correct dotnet executable path&#10;ExecStart=/usr/bin/dotnet /srv/HelloWorld/HelloWorld.dll&#10;# to query logs using journalctl, set a logical name here&#10;SyslogIdentifier=HelloWorld&#10;&#10;# Use your username to keep things simple.&#10;# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app&#10;# To update permissions, use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chown yourusername -R /srv/HelloWorld</span><span style="color:#2a00ff; ">'</span> to take ownership of the folder and files,&#10;#       Use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chmod +x /srv/HelloWorld/HelloWorld</span><span style="color:#2a00ff; ">'</span> to allow execution of the executable file&#10;User=yourusername&#10;&#10;<b># ensure the service restarts after crashing&#10;Restart=always&#10;# amount of time to wait before restarting the service                        &#10;RestartSec=5  </b> &#10;&#10;# This environment variable is necessary when dotnet isn<span style="color:#2a00ff; ">'</span>t loaded for the specified user.&#10;# To figure out this value, run <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">env | grep DOTNET_ROOT</span><span style="color:#2a00ff; ">'</span> when dotnet has been loaded into your shell.&#10;Environment=DOTNET_ROOT=/usr/lib64/dotnet&#10;&#10;[Install]&#10;WantedBy=multi-user.target&#10;</pre>

Copy the service file, reload, and restart the service:

<pre style="color:#000000;background:#ffffff;">sudo cp HelloWorld<span style="color:#800000; font-weight:bold; ">.</span>service <span style="color:#40015a; ">/etc/systemd/system/HelloWorld.service</span>&#10;sudo systemctl daemon-reload&#10;sudo systemctl restart HelloWorld&#10;</pre>

Now the service will automatically restart in case of a crash. But when the OS reboots, the application will not automatically start. To enable automatic startup of the service on boot, run the following command:

<pre style="color:#000000;background:#ffffff;">sudo systemctl <span style="color:#bb7977; font-weight:bold; ">enable</span> HelloWorld&#10;</pre>

This console app works fine, but Microsoft has provided the **worker template** which is a more robust solution for long running services/daemons. Let's upgrade to using the worker template next.

## .NET Core worker template {#net-core-worker-template}

Let's create a new empty directory and create the worker using the dotnet CLI:

<pre style="color:#000000;background:#ffffff;">mkdir ~<span style="color:#40015a; ">/WorkerApp</span>&#10;<span style="color:#bb7977; font-weight:bold; ">cd</span> ~<span style="color:#40015a; ">/WorkerApp</span>&#10;dotnet new worker&#10;</pre>




Verify the worker is functional using the command `dotnet run`.<br>If the application works, publish it somewhere logical such as */srv/WorkerApp*:




<pre style="color:#000000;background:#ffffff;">sudo mkdir <span style="color:#40015a; ">/srv/WorkerApp</span>                  <span style="color:#696969; "># Create directory /srv/WorkerApp</span>&#10;sudo chown yourusername <span style="color:#40015a; ">/srv/WorkerApp</span>     <span style="color:#696969; "># Assign ownership to yourself of the directory</span>&#10;dotnet publish <span style="color:#44aadd; ">-c</span> Release <span style="color:#44aadd; ">-o</span> <span style="color:#40015a; ">/srv/WorkerApp</span>&#10;</pre>

Let's verify we can also run the published application:

<pre style="color:#000000;background:#ffffff;"><span style="color:#40015a; ">/srv/WorkerApp/WorkerApp</span>&#10;<span style="color:#696969; "># Output should look like this:</span>&#10;<span style="color:#696969; ">#   info: WorkerApp.WorkerApp[0]</span>&#10;<span style="color:#696969; ">#         WorkerApp running at: 01/28/2020 15:08:56 +00:00</span>&#10;<span style="color:#696969; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#696969; ">#         Application started. Press Ctrl+C to shut down.</span>&#10;<span style="color:#696969; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#696969; ">#         Hosting environment: Production</span>&#10;<span style="color:#696969; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#696969; ">#         Content root path: /home/yourusername/WorkerApp</span>&#10;<span style="color:#696969; ">#   info: WorkerApp.WorkerApp[0]</span>&#10;<span style="color:#696969; ">#         WorkerApp running at: 01/28/2020 15:08:57 +00:00</span>&#10;<span style="color:#696969; ">#   info: WorkerApp.WorkerApp[0]</span>&#10;<span style="color:#696969; ">#         WorkerApp running at: 01/28/2020 15:08:58 +00:00</span>&#10;</pre>




Create a service unit configuration file called "WorkerApp.service" inside our project:




<pre style="color:#000000;background:#ffffff;">[Unit]&#10;Description=Long running service/daemon created from .NET worker template&#10;&#10;[Service]&#10;# will set the Current Working Directory (CWD). Worker service will have issues without this setting&#10;WorkingDirectory=/srv/WorkerApp&#10;# systemd will run this executable to start the service&#10;# if /usr/bin/dotnet doesn<span style="color:#2a00ff; ">'</span>t work, use `which dotnet` to find correct dotnet executable path&#10;ExecStart=/usr/bin/dotnet /srv/WorkerApp/WorkerApp.dll&#10;# to query logs using journalctl, set a logical name here&#10;SyslogIdentifier=WorkerApp&#10;&#10;# Use your username to keep things simple.&#10;# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app&#10;# To update permissions, use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chown yourusername -R /srv/WorkerApp</span><span style="color:#2a00ff; ">'</span> to take ownership of the folder and files,&#10;#       Use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chmod +x /srv/WorkerApp/WorkerApp</span><span style="color:#2a00ff; ">'</span> to allow execution of the executable file&#10;User=yourusername&#10;&#10;# ensure the service restarts after crashing&#10;Restart=always&#10;# amount of time to wait before restarting the service                  &#10;RestartSec=5&#10;&#10;# This environment variable is necessary when dotnet isn<span style="color:#2a00ff; ">'</span>t loaded for the specified user.&#10;# To figure out this value, run <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">env | grep DOTNET_ROOT</span><span style="color:#2a00ff; ">'</span> when dotnet has been loaded into your shell.&#10;Environment=DOTNET_ROOT=/usr/lib64/dotnet  &#10;&#10;[Install]&#10;WantedBy=multi-user.target&#10;</pre>




Copy the service configuration file to <em><strong>/etc/systemd/system/WorkerApp.service</strong></em> and tell systemd to reload the configuration files:




<pre style="color:#000000;background:#ffffff;">sudo cp WorkerApp<span style="color:#800000; font-weight:bold; ">.</span>service <span style="color:#40015a; ">/etc/systemd/system/WorkerApp.service</span>&#10;sudo systemctl daemon-reload&#10;sudo systemctl start WorkerApp&#10;</pre>

Using 'journalctl', we can verify that the application is contentiously running successfully. The following 'journalctl' command will follow the output of the application. Use Ctrl-C to exit the command.

<pre style="color:#000000;background:#ffffff;">sudo journalctl <span style="color:#44aadd; ">-u</span> WorkerApp <span style="color:#44aadd; ">-f</span>&#10;</pre>

The .NET Core worker now runs as a systemd service, but the integration between .NET Core and systemd can bi improved on by installing the systemd-integration.

## Adding .NET Core Systemd integration {#adding-net-core-systemd-integration}

Microsoft recently added a package to better integrate with systemd. The .NET Core application will notify systemd when it's ready and when it's stopping. Additionally, systemd will now understand the different log levels when the .NET Core application logs to output.


Using the dotnet CLI, add the 'Microsoft.Extensions.Hosting.Systemd' (<a rel="noopener" href="https://www.nuget.org/packages/Microsoft.Extensions.Hosting.Systemd/" target="_blank">nuget</a>) package:




<pre style="color:#000000;background:#ffffff;">dotnet add package Microsoft<span style="color:#800000; font-weight:bold; ">.</span>Extensions<span style="color:#800000; font-weight:bold; ">.</span>Hosting<span style="color:#800000; font-weight:bold; ">.</span>Systemd&#10;</pre>




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




<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; font-weight:bold; ">using</span> WorkerApp;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> Microsoft.Extensions.Hosting;&#10;&#10;IHost host = Host.CreateDefaultBuilder(args)&#10;	.UseSystemd()&#10;	.ConfigureServices(services =&gt;&#10;	{&#10;		services.AddHostedService&lt;Worker&gt;();&#10;	})&#10;	.Build();&#10;</pre>

Lastly, we need to update our service unit configuration file to specify 'type=Notify':

<pre style="color:#000000;background:#ffffff;">[Unit]&#10;Description=Long running service/daemon created from .NET worker template&#10;&#10;[Service]&#10;Type=notify&#10;# will set the Current Working Directory (CWD). Worker service will have issues without this setting&#10;WorkingDirectory=/srv/WorkerApp&#10;# systemd will run this executable to start the service&#10;# if /usr/bin/dotnet doesn<span style="color:#2a00ff; ">'</span>t work, use `which dotnet` to find correct dotnet executable path&#10;ExecStart=/usr/bin/dotnet /srv/WorkerApp/WorkerApp.dll&#10;# to query logs using journalctl, set a logical name here&#10;SyslogIdentifier=WorkerApp&#10;&#10;# Use your username to keep things simple.&#10;# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app&#10;# To update permissions, use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chown yourusername -R /srv/WorkerApp</span><span style="color:#2a00ff; ">'</span> to take ownership of the folder and files,&#10;#       Use <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">chmod +x /srv/WorkerApp/WorkerApp</span><span style="color:#2a00ff; ">'</span> to allow execution of the executable file&#10;User=yourusername&#10;&#10;# ensure the service restarts after crashing&#10;Restart=always&#10;# amount of time to wait before restarting the service                  &#10;RestartSec=5&#10;&#10;# This environment variable is necessary when dotnet isn<span style="color:#2a00ff; ">'</span>t loaded for the specified user.&#10;# To figure out this value, run <span style="color:#2a00ff; ">'</span><span style="color:#2a00ff; ">env | grep DOTNET_ROOT</span><span style="color:#2a00ff; ">'</span> when dotnet has been loaded into your shell.&#10;Environment=DOTNET_ROOT=/usr/lib64/dotnet  &#10;&#10;[Install]&#10;WantedBy=multi-user.target&#10;</pre>

Let's publish, reload, and restart the service:

<pre style="color:#000000;background:#ffffff;">sudo systemctl stop WorkerApp <span style="color:#696969; "># stop service to release any file locks which could conflict with dotnet publish</span>&#10;dotnet publish <span style="color:#44aadd; ">-c</span> Release <span style="color:#44aadd; ">-o</span> <span style="color:#40015a; ">/srv/WorkerApp</span>&#10;sudo cp WorkerApp<span style="color:#800000; font-weight:bold; ">.</span>service <span style="color:#40015a; ">/etc/systemd/system/WorkerApp.service</span>&#10;sudo systemctl daemon-reload&#10;sudo systemctl start WorkerApp  &#10;</pre>

With the Systemd integration in place, we can now use the priority-flag (-p) on 'journalctl' to filter the output according to the log levels below:

<table border="0" style="height: 108px;">
<tbody>
<tr style="height: 18px;">
<td style="height: 18px;"><strong>LogLevel</strong></td>
<td style="height: 18px;"><strong>Syslog level</strong></td>
<td style="height: 18px;"><strong>systemd name</strong></td>
</tr>
<tr style="height: 18px;">
<td style="height: 18px;">Trace/Debug</td>
<td style="height: 18px;">7</td>
<td style="height: 18px;">debug</td>
</tr>
<tr style="height: 18px;">
<td style="height: 18px;">Information</td>
<td style="height: 18px;">6</td>
<td style="height: 18px;">info</td>
</tr>
<tr style="height: 18px;">
<td style="height: 18px;">Warning</td>
<td style="height: 18px;">4</td>
<td style="height: 18px;">warning</td>
</tr>
<tr style="height: 18px;">
<td style="height: 18px;">Error</td>
<td style="height: 18px;">3</td>
<td style="height: 18px;">err</td>
</tr>
<tr style="height: 18px;">
<td style="height: 18px;">Critical</td>
<td style="height: 18px;">2</td>
<td class="" style="height: 18px;">crit</td>
</tr>
</tbody>
</table>

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

<pre style="color:#000000;background:#ffffff;">sudo journalctl <span style="color:#44aadd; ">-u</span> WorkerApp <span style="color:#44aadd; ">-f</span> <span style="color:#44aadd; ">-p</span> <span style="color:#008c00; ">4</span>&#10;</pre>

We won't see much because there's nothing being logged as a warning, error, or critical.<br>Update the 'WorkerApp.cs' file to include 'LogWarning', 'LogError', 'LogCritical' and republish:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; font-weight:bold; ">namespace</span> WorkerApp;&#10;&#10;<span style="color:#7f0055; font-weight:bold; ">public</span> <span style="color:#7f0055; font-weight:bold; ">class</span> Worker : BackgroundService&#10;{&#10;	<span style="color:#7f0055; font-weight:bold; ">private</span> <span style="color:#7f0055; font-weight:bold; ">readonly</span> ILogger&lt;Worker&gt; _logger;&#10;&#10;	<span style="color:#7f0055; font-weight:bold; ">public</span> Worker(ILogger&lt;Worker&gt; logger)&#10;	{&#10;		_logger = logger;&#10;	}&#10;&#10;	<span style="color:#7f0055; font-weight:bold; ">protected</span> <span style="color:#7f0055; font-weight:bold; ">override</span> async Task ExecuteAsync(CancellationToken stoppingToken)&#10;	{&#10;		<span style="color:#7f0055; font-weight:bold; ">while</span> (!stoppingToken.IsCancellationRequested)&#10;		{&#10;&#10;			_logger.LogInformation(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Information - Worker running at: {time}</span><span style="color:#2a00ff; ">"</span>, DateTimeOffset.Now);&#10;			_logger.LogWarning(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Warning - Worker running at: {time}</span><span style="color:#2a00ff; ">"</span>, DateTimeOffset.Now);&#10;			_logger.LogError(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Error - Worker running at: {time}</span><span style="color:#2a00ff; ">"</span>, DateTimeOffset.Now);&#10;			_logger.LogCritical(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Critical - Worker running at: {time}</span><span style="color:#2a00ff; ">"</span>, DateTimeOffset.Now);&#10;			await Task.Delay(1000, stoppingToken);&#10;		}&#10;	}&#10;}&#10;</pre>

Republish and restart the service:

<pre style="color:#000000;background:#ffffff;">sudo systemctl stop WorkerApp <span style="color:#696969; "># stop service to release any file locks which could conflict with dotnet publish</span>&#10;dotnet publish <span style="color:#44aadd; ">-c</span> Release <span style="color:#44aadd; ">-o</span> <span style="color:#40015a; ">/srv/WorkerApp</span>&#10;sudo systemctl start WorkerApp&#10;</pre>

When we run the same 'journalctl' command, we can now see the warning output as bold white text, the error and critical output as red bold text.

<pre style="color:#000000;background:#ffffff;">sudo journalctl <span style="color:#44aadd; ">-u</span> WorkerApp <span style="color:#44aadd; ">-f</span> <span style="color:#44aadd; ">-p</span> <span style="color:#008c00; ">4</span>&#10;<span style="color:#696969; "># Output should look like this:</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:02 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Warning - WorkerApp running at: 01/28/2020 18:17:02 +00:00</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:02 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Error - WorkerApp running at: 01/28/2020 18:17:02 +00:00</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:02 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Critical - WorkerApp running at: 01/28/2020 18:17:02 +00:00</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:03 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Warning - WorkerApp running at: 01/28/2020 18:17:03 +00:00</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:03 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Error - WorkerApp running at: 01/28/2020 18:17:03 +00:00</span>&#10;<span style="color:#696969; ">#   Jan 28 18:17:03 rhtest WorkerApp[33537]: WorkerApp.WorkerApp[0] Critical - WorkerApp running at: 01/28/2020 18:17:03 +00:00</span>&#10;</pre>



<p class="panel panel--warning">The 'UseSystemd' function will not do anything when run outside of a systemd service.<br>The <a rel="noopener" href="https://github.com/dotnet/extensions/blob/master/src/Hosting/Systemd/src/SystemdHelpers.cs#L29-L44" target="_blank">implementation checks</a> if the OS is a Unix system and whether the parent process is systemd.<br>If not, the systemd integration is skipped.</p>



## Summary {#summary}




.NET Core has good support for running services on Linux. Using the worker template, we can create a long running service/daemon that integrates well with systemd. Using the systemd hosting integration, systemd is notified when the .NET Core application is ready & also understand the different log levels in .NET.


The setup for ASP.NET Core apps is very similar, though it'll take some extra steps to ensure the web application is accessible to other machines in your network or the internet.
