
ASP.NET web development framework
Common Core
Small and large web applications are becoming increasingly common in the enterprise. Many administrators program small tools to optimize existing system management, and Microsoft's .NET Core now makes this possible across operating system boundaries. In this article, I look at the part of the .NET Core functionality that enables the development and operation of dynamic web applications – ASP.NET Core – and its use on Linux, with a focus on the peculiarities of providing ASP.Net Core applications and not on their application logic or graphical design.
.NET Core Genesis
At the beginning of the century, Microsoft heralded a new era in their software development history with the announcement of the .NET Framework. A promise was made that the framework would not be exclusively reserved for the Windows platform. After all, the write once, run anywhere (WORA) principle was one of the factors that had already helped Java achieve great success as an enterprise platform.
Microsoft did not pursue the "Linux.NET" idea any further although several open source projects emerged to enable seamless porting of .NET applications to Linux and Unix. The best known and longest lasting of these projects was Mono, developed and maintained by the core team that later created Xamarin; however, Mono never achieved true feature parity with the official .NET.
The deep divide with Linux and open source seems to have largely been overcome at Microsoft. Xamarin is now also based in Redmond and the company's products have been incorporated into Visual Studio as components for the development of mobile applications. In view of this openness toward other platforms, the idea of extending the leading development framework to Linux, macOS, and potentially CPU architectures other than x64 and x86 was obvious.
Microsoft decided to redevelop the framework completely, with an open source and cross-platform design from the beginning. Not surprisingly, the manufacturer left out many Windows-specific frameworks that were docked to .NET in the course of development and concentrated on the core functionality – .NET Core was born. Many administrators first encountered the new .NET in the form of the PowerShell Core, which is also open source and available on many operating systems.
Web Apps as Servers
On the Internet Information Server (IIS), where ASP.NET is at home, the .NET code runs in application pools. The IIS web worker handles network communication with the client, TLS encryption, and authentication; the application pool receives "pre-processed" requests, processes them, and passes the results back to the web worker, which then makes it available to the browser.
.NET Core is based on a different architecture. Instead of being integrated into a web server as a kind of extension, as is the case with PHP or classic ASP, every web application under .NET Core is a full-fledged web server. It listens even to client requests and transmits the web content prepared by the .NET Core program code to the browser. This structure is not unlike its Java counterpart Tomcat. The .NET Core web server has been given the code name "Kestrel", which is also found in the object model of ASP.NET Core.
By the way, a compiled ASP.NET Core application is not portable, in that it still requires the .NET Core runtime on the target machine in the version against which the application was originally compiled. At the time of print, version 3.1 of the framework is up to date. All code examples in this article refer to that version. The successor version will be numbered 5 and is already available as a preview. Microsoft intends to merge the "original" .NET framework – which is currently also approaching version 5, at version 4.8 – with the open source .NET Core framework [1].
Creating an Initial App
The fastest way to get to know the Kestrel Worker is to install the .NET Core Software Development Kit (SDK) and create a sample app. On CentOS 8 and current Red Hat and Fedora versions, you can enter:
sudo dnf install dotnet-sdk-3.1
Installing on other supported Linux distributions is documented online [2]. If you are planning for production use, please note the rather strict Microsoft support guidelines. Once the SDK has been installed successfully, you can create and start a webapp in any directory from one of the numerous templates provided:
mkdir testwebapp cd testwebapp dotnet new mvc dotnet run
If your Linux machine has a GUI, you can access the new website on https://localhost:5001 in your browser (Figure 1).

Of course, neither a GUI nor a complete .NET development environment will be installed on a production Linux web server. Before you can call the new test app from another machine, you still have to overcome some hurdles. First, in the default installation, Kestrel explicitly creates a binding to localhost. You can change this by adding the following line to the Program.cs
file with the appropriate parameter:
webBuilder.UseStartup<Startup>().UseUrls(new[] { "https://*:4711" });
The Kestrel instance now listens on port 4711 on any IP address. The next hurdle is the local firewall of the Linux server. In most common distributions, it is activated and only lets SSH traffic through. You have to open the selected port to reach your new app:
sudo firewall-cmd --permanent--zone=public --add-port=4711/tcp sudo firewall-cmd --reload
Connection Security with TLS
The configuration just created uses a self-signed certificate (Figure 2). To bind a trusted certificate to the Kestrel instance, it must be configured slightly differently at startup. Instead of the simple specifications in .UseStartup()
, as described above, you need to adjust the CreateHostBuilder
function as shown in Listing 1.
Listing 1: CreateHostBuilder
01 public static IHostBuilder CreateHostBuilder(string[] args) => 02 Host.CreateDefaultBuilder(args) 03 .ConfigureWebHostDefaults(webBuilder => 04 { 05 webBuilder.ConfigureKestrel(serverOptions => 06 { 07 serverOptions.ListenAnyIP(4711, 08 listenOptions => { 09 listenOptions.UseHttps("cert.pfx", "<password>"); 10 } 11 ); 12 }) 13 .UseStaticWebAssets() 14 .UseStartup<Startup>(); 15 });

Much like site bindings on the IIS, the port listened on and the certificate to be used are defined as parts of the same configuration. The PFX file with the TLS certificate must be located in the root directory of the web application. The password is in plain text in the C# file; therefore, it is important that unauthorized persons do not gain access to this file.
The listenOptions
class has many properties that can be used to influence the behavior of the Kestrel Worker [3]. The storage path of the PFX file is important later when you run the fully compiled application on a server without the .NET Core SDK. The path is always relative to the defined working directory. You should not confuse this with the location of the compiled web application.
The UseStaticWebAssets
method indicates that, in addition to the content generated by the application, static web content such as CSS stylesheets, graphics files, and browser scripts should be delivered, as well. By default, these are located in the wwwroot
directory – also relative to the current working directory.
Doing Without Windows Components
To develop your .NET Core application, you can use Microsoft's proven Visual Studio, which is available for free as the Community Edition – as long as you do not create applications for organizations with more than 1,000 users. If you select the Web Development Component, you will find many project templates for .NET Core and ASP.NET Core (Figure 3).

To begin, develop your application in the usual way and make sure you do not include any components specific to Windows. When the application is ready, compile it for the target platform. You will need either a Linux machine with the .NET Core SDK installed or a Linux instance in the Windows Subsystem for Linux (WSL), also with the .NET Core SDK on board.
Next, copy the source code of your application to the Linux instance, change to the project directory, and run:
dotnet build
By default, a debug build is generated; if you want a release build, specify -c Release
as a parameter. The result will end up, as in Windows, in /bin/Debug/netcoreapp3.1
or /bin/Release/netcoreapp3.1
. After copying the static files from wwwroot
and other content such as PFX files into the build directory, and you will have a ready-to-run application that you can execute on a Linux machine without a .NET-Core SDK – you only need the correct version of the runtime. The file to be executed has a DLL extension, which is not typical for Linux.
You can and will want to automate the process of taking the necessary files and directories with you by adding all necessary components to your Visual Studio project and then starting the build process with:
dotnet publish -c Release
You will find the complete app in /bin/Release/netcoreapp3.1/publish
.
Launch the Apps
Unlike the pool of applications on IIS, with launching and lifecycles managed by the WWW Publishing Service, a Kestrel web application is a standalone process and is not controlled by anything. If you want your new web application to launch automatically when the server is restarted, you need to take action yourself and register the application as a service. You need to create a service user for the service first if a suitable account does not already exist on your Linux server:
sudo add user netcoreapp01
Then, copy the application from your build machine to the home directory of the service user. You can also use another directory (e.g., /var/www
), but then you have to grant the service user read and execute permissions manually. Now create the service object for your service, such as
sudo nano /etc/systemd/system/netcoreapp01.service
(but with the editor of your choice). The file must have at least the content shown in Listing 2.
Listing 2: netcoreapp01.service
[Unit] Description=My first .NET Core web app [Service] WorkingDirectory=/home/netcoreapp01 ExecStart=/usr/bin/dotnet /home/netcoreapp01/netcoreapp01.dll Restart=always RestartSec=10 KillSignal=SIGINT SyslogIdentifier=netcoreapp01 User=netcoreapp01 Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target
Next, save the file and check whether the service starts:
sudo systemctl start netcoreapp01
You can view the result the same way but with the status
instead of the start
command. If everything works as desired, set up an automatic start, again with the same format, but with the enable
command.
Now your application will load automatically, even after a server reboot.
Assigning Ports
A simple Kestrel application with default settings always binds to port 5000 for HTTP and port 5001 for HTTPS. Because the ASP.NET Core has no TCP port sharing, as in the original .NET framework, the port is now busy and not available for other processes. Also, the .NET runtime does not implement a mechanism to increment TCP ports automatically when other applications start on the same machine. Therefore, you have to keep track of the port assignments yourself and, if possible, store them in the configuration file of your Visual Studio project so that you don't have to rebuild the application every time you make changes. This characteristic of Kestrel is not Linux-specific, but also applies to Windows.
In professional use, you will of course run several applications on the same server, and in the best case, they should all be accessible on the standard HTTPS port 443. Additional security features such as pre-authentication or content caching, which Kestrel lacks, are also essential in enterprise scenarios. This is where true web servers like Apache, Nginx, or (on Windows) IIS come in, which you can add to Kestrel to gain the missing features.
In this way, the Kestrel instances can be operated as a kind of mixture of application pool and web worker. The upstream web server handles the requests and forwards them to the correct Kestrel instance on the respective port. This mode of operation is known as "reverse proxy." TLS processing can also be offloaded to the full-fledged server in this way, which is especially valuable if the TLS certificates to be used change frequently or if you want to deploy the application to a large number of machines. In this case, you only need to maintain the certificate for all application instances of a web server on the frontend server. However, communication between the frontend web server and Kestrel instances takes place without encryption, which is normally not a good choice.
Hosting Models for ASP.NET
The origin of ASP.NET in the IIS universe is illustrated by the two models that IIS provides for hosting ASP.NET core applications:
- The out-of-process hosting model works exactly as outlined above and is very well suited for applications that you want to run on Linux and IIS. Here, IIS simply routes client requests to the Kestrel application, with no need to customize the application itself, because IIS integration is configured in the release properties.
- The in-process hosting model involves running the ASP.NET Core applications in an IIS (Application Pool) worker process. The Windows Process Activation Service (WAS) is responsible for control. Here, you have to adapt the source code of the application to be hosted on, instead of downstream of, IIS. To do this, you set additional configuration options in the Host Builder that have no function outside of IIS.
Regardless of the hosting model, Core middleware and the ASP.NET Core module are required on the affected IIS. The necessary adjustments to the application itself and to the configuration of the IIS instance are described in detail in Microsoft Docs [4]. Visual Studio also offers the possibility of testing the development states of your ASP.NET Core application in IIS (Express).
If a special module in IIS handles the entire integration of ASP.NET Core into the web server, the frontend web server on Linux only plays the role of a reverse proxy. For the reverse proxy to handle requests correctly, additional headers (e.g., X-Forwarded-For
and X-Forwarded-Proto
) must be interpreted by the Kestrel application. You also need this customization if the application is delivered purely by Kestrel but is deployed with a load balancer. To do this, integrate the Microsoft.AspNetCore.HttpOverrides
middleware into your project. Then, you can reference this namespace in your Startup.cs
file and add the following call to the Configure()
method:
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto });
In this article, I assume that the reverse proxy is running on the same system as the Kestrel application and is therefore trusted implicitly. Otherwise, the addresses of the trusted proxies must be added to the application.
Using an Apache Frontend
If your Kestrel application is installed on a Linux server, you can add an Apache frontend by installing the appropriate packages:
sudo dnf install httpd mod_ssl
I used CentOS 8 as an example; other Linux distributions may have a different package manager. If no SSL is required, an additional file named /etc/httpd/conf.d/netcoreapp01.conf
with the content shown in Listing 3 is sufficient.
Listing 3: netcoreapp01.conf
<VirtualHost *:*> RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME} </VirtualHost> <VirtualHost *:80> ProxyPreserveHost On ProxyPass / http://127.0.0.1:4711/ ProxyPassReverse / http://127.0.0.1:4711/ ServerName www.example.com ServerAlias *.example.com ErrorLog ${APACHE_LOG_DIR}netcoreapp01-error.log CustomLog ${APACHE_LOG_DIR}netcoreapp01-access.log common </VirtualHost>
After subsequently restarting the HTTPD service, the application is available on port 80 tunneled through Apache. The SSL configuration follows a similar approach and is described in the official Apache documentation, along with countless books and Internet articles. The necessary adjustments to the ASP.NET Core application are documented online [5], where you will also find notes on the above-mentioned middleware packages and its integration into your source code.
Nginx as a Reverse Proxy
Originally developed for the Russian search engine Rambler, Nginx has become the standard for reverse proxy and load balancing of websites and application servers with high traffic. The extremely frugal use of server resources by Nginx also makes this product attractive for internal use. To install on my CentOS 8 example system, I typed:
sudo dnf install nginx
Although the configuration files have a different format, the minimum configuration required is the same as for Apache. You will find this information in the Microsoft documentation [6]. Again, further refinements like context switches, TLS bridging, and so on require in-depth Nginx knowledge and must be documented.
Conclusions
In recent years, Microsoft has opened up considerably to the open source community. The ability to run .NET Core on Linux is one example. With the open source and platform-independent framework, you can provide web applications on Windows and Linux from your familiar development environment. The ASP.NET knowledge that you have acquired so far through the development of your tools will benefit you here.
Depending on the security and performance requirements of your applications, you can use the native Kestrel web worker directly or publish it behind a full-fledged web server operated as a reverse proxy (IIS on Windows, Nginx or Apache on Linux). Because all of these integration scenarios require loading NuGet packages, in addition to the project templates, your development workstation should have access to the Internet, at least temporarily.