Host ASP.NET Core on Linux with Apache

By Shayne Boyer

Using this guide, learn how to set up Apache as a reverse proxy server on CentOS 7 to redirect HTTP traffic to an ASP.NET Core web app running on Kestrel. The mod_proxy extension and related modules create the server's reverse proxy.

Prerequisites

  1. Server running CentOS 7 with a standard user account with sudo privilege
  2. ASP.NET Core app

Publish the app

Publish the app as a self-contained deployment in Release configuration for the CentOS 7 runtime (centos.7-x64). Copy the contents of the bin/Release/netcoreapp2.0/centos.7-x64/publish folder to the server using SCP, FTP, or other file transfer method.

Note

Under a production deployment scenario, a continuous integration workflow does the work of publishing the app and copying the assets to the server.

Configure a proxy server

A reverse proxy is a common setup for serving dynamic web apps. The reverse proxy terminates the HTTP request and forwards it to the ASP.NET app.

A proxy server is one which forwards client requests to another server instead of fulfilling requests itself. A reverse proxy forwards to a fixed destination, typically on behalf of arbitrary clients. In this guide, Apache is configured as the reverse proxy running on the same server that Kestrel is serving the ASP.NET Core app.

Because requests are forwarded by reverse proxy, use the Forwarded Headers Middleware from the Microsoft.AspNetCore.HttpOverrides package. The middleware updates the Request.Scheme, using the X-Forwarded-Proto header, so that redirect URIs and other security policies work correctly.

When using any type of authentication middleware, the Forwarded Headers Middleware must run first. This ordering ensures that the authentication middleware can consume the header values and generate correct redirect URIs.

Invoke the UseForwardedHeaders method in Startup.Configure before calling UseAuthentication or similar authentication scheme middleware. Configure the middleware to forward the X-Forwarded-For and X-Forwarded-Proto headers:

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

If no ForwardedHeadersOptions are specified to the middleware, the default headers to forward are None.

Additional configuration might be required for apps hosted behind proxy servers and load balancers. For more information, see Configure ASP.NET Core to work with proxy servers and load balancers.

Install Apache

Update CentOS packages to their latest stable versions:

sudo yum update -y

Install the Apache web server on CentOS with a single yum command:

sudo yum -y install httpd mod_ssl

Sample output after running the command:

Downloading packages:
httpd-2.4.6-40.el7.centos.4.x86_64.rpm               | 2.7 MB  00:00:01
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : httpd-2.4.6-40.el7.centos.4.x86_64      1/1 
Verifying  : httpd-2.4.6-40.el7.centos.4.x86_64      1/1 

Installed:
httpd.x86_64 0:2.4.6-40.el7.centos.4

Complete!

Note

In this example, the output reflects httpd.86_64 since the CentOS 7 version is 64 bit. To verify where Apache is installed, run whereis httpd from a command prompt.

Configure Apache for reverse proxy

Configuration files for Apache are located within the /etc/httpd/conf.d/ directory. Any file with the .conf extension is processed in alphabetical order in addition to the module configuration files in /etc/httpd/conf.modules.d/, which contains any configuration files necessary to load modules.

Create a configuration file, named hellomvc.conf, for the app:

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ServerName www.example.com
    ServerAlias *.example.com
    ErrorLog ${APACHE_LOG_DIR}hellomvc-error.log
    CustomLog ${APACHE_LOG_DIR}hellomvc-access.log common
</VirtualHost>

The VirtualHost block can appear multiple times, in one or more files on a server. In the preceding configuration file, Apache accepts public traffic on port 80. The domain www.example.com is being served, and the *.example.com alias resolves to the same website. See Name-based virtual host support for more information. Requests are proxied at the root to port 5000 of the server at 127.0.0.1. For bi-directional communication, ProxyPass and ProxyPassReverse are required.

Warning

Failure to specify a proper ServerName directive in the VirtualHost block exposes your app to security vulnerabilities. Subdomain wildcard binding (for example, *.example.com) doesn't pose this security risk if you control the entire parent domain (as opposed to *.com, which is vulnerable). See rfc7230 section-5.4 for more information.

Logging can be configured per VirtualHost using ErrorLog and CustomLog directives. ErrorLog is the location where the server logs errors, and CustomLog sets the filename and format of log file. In this case, this is where request information is logged. There's one line for each request.

Save the file and test the configuration. If everything passes, the response should be Syntax [OK].

sudo service httpd configtest

Restart Apache:

sudo systemctl restart httpd
sudo systemctl enable httpd

Monitoring the app

Apache is now setup to forward requests made to http://localhost:80 to the ASP.NET Core app running on Kestrel at http://127.0.0.1:5000. However, Apache isn't set up to manage the Kestrel process. Use systemd and create a service file to start and monitor the underlying web app. systemd is an init system that provides many powerful features for starting, stopping, and managing processes.

Create the service file

Create the service definition file:

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

An example service file for the app:

[Unit]
Description=Example .NET Web API App running on CentOS 7

[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
# Restart service after 10 seconds if dotnet service crashes
RestartSec=10
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production 

[Install]
WantedBy=multi-user.target

Note

User — If the user apache isn't used by the configuration, the user must be created first and given proper ownership for files.

Note

Some values (for example, SQL connection strings) must be escaped for the configuration providers to read the environment variables. Use the following command to generate a properly escaped value for use in the configuration file:

systemd-escape "<value-to-escape>"

Save the file and enable the service:

systemctl enable kestrel-hellomvc.service

Start the service and verify that it's running:

systemctl start kestrel-hellomvc.service
systemctl status kestrel-hellomvc.service

● kestrel-hellomvc.service - Example .NET Web API App running on CentOS 7
    Loaded: loaded (/etc/systemd/system/kestrel-hellomvc.service; enabled)
    Active: active (running) since Thu 2016-10-18 04:09:35 NZDT; 35s ago
Main PID: 9021 (dotnet)
    CGroup: /system.slice/kestrel-hellomvc.service
            └─9021 /usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll

With the reverse proxy configured and Kestrel managed through systemd, the web app is fully configured and can be accessed from a browser on the local machine at http://localhost. Inspecting the response headers, the Server header indicates that the ASP.NET Core app is served by Kestrel:

HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked

Viewing logs

Since the web app using Kestrel is managed using systemd, events and processes are logged to a centralized journal. However, this journal includes entries for all of the services and processes managed by systemd. To view the kestrel-hellomvc.service-specific items, use the following command:

sudo journalctl -fu kestrel-hellomvc.service

For time filtering, specify time options with the command. For example, use --since today to filter for the current day or --until 1 hour ago to see the previous hour's entries. For more information, see the man page for journalctl.

sudo journalctl -fu kestrel-hellomvc.service --since "2016-10-18" --until "2016-10-18 04:00"

Securing the app

Configure firewall

Firewalld is a dynamic daemon to manage the firewall with support for network zones. Ports and packet filtering can still be managed by iptables. Firewalld should be installed by default. yum can be used to install the package or verify it's installed.

sudo yum install firewalld -y

Use firewalld to open only the ports needed for the app. In this case, port 80 and 443 are used. The following commands permanently set ports 80 and 443 to open:

sudo firewall-cmd --add-port=80/tcp --permanent
sudo firewall-cmd --add-port=443/tcp --permanent

Reload the firewall settings. Check the available services and ports in the default zone. Options are available by inspecting firewall-cmd -h.

sudo firewall-cmd --reload
sudo firewall-cmd --list-all
public (default, active)
interfaces: eth0
sources: 
services: dhcpv6-client
ports: 443/tcp 80/tcp
masquerade: no
forward-ports: 
icmp-blocks: 
rich rules: 

SSL configuration

To configure Apache for SSL, the mod_ssl module is used. When the httpd module was installed, the mod_ssl module was also installed. If it wasn't installed, use yum to add it to the configuration.

sudo yum install mod_ssl

To enforce SSL, install the mod_rewrite module to enable URL rewriting:

sudo yum install mod_rewrite

Modify the hellomvc.conf file to enable URL rewriting and secure communication on port 443:

<VirtualHost *:80>
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/?(.*) https://%{SERVER_NAME}/ [R,L]
</VirtualHost>

<VirtualHost *:443>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ErrorLog /var/log/httpd/hellomvc-error.log
    CustomLog /var/log/httpd/hellomvc-access.log common
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
    SSLCertificateFile /etc/pki/tls/certs/localhost.crt
    SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>

Note

This example is using a locally-generated certificate. SSLCertificateFile should be the primary certificate file for the domain name. SSLCertificateKeyFile should be the key file generated when CSR is created. SSLCertificateChainFile should be the intermediate certificate file (if any) that was supplied by the certificate authority.

Save the file and test the configuration:

sudo service httpd configtest

Restart Apache:

sudo systemctl restart httpd

Additional Apache suggestions

Additional headers

In order to secure against malicious attacks, there are a few headers that should either be modified or added. Ensure that the mod_headers module is installed:

sudo yum install mod_headers

Secure Apache from clickjacking attacks

Clickjacking, also known as a UI redress attack, is a malicious attack where a website visitor is tricked into clicking a link or button on a different page than they're currently visiting. Use X-FRAME-OPTIONS to secure the site.

Edit the httpd.conf file:

sudo nano /etc/httpd/conf/httpd.conf

Add the line Header append X-FRAME-OPTIONS "SAMEORIGIN". Save the file. Restart Apache.

MIME-type sniffing

The X-Content-Type-Options header prevents Internet Explorer from MIME-sniffing (determining a file's Content-Type from the file's content). If the server sets the Content-Type header to text/html with the nosniff option set, Internet Explorer renders the content as text/html regardless of the file's content.

Edit the httpd.conf file:

sudo nano /etc/httpd/conf/httpd.conf

Add the line Header set X-Content-Type-Options "nosniff". Save the file. Restart Apache.

Load Balancing

This example shows how to setup and configure Apache on CentOS 7 and Kestrel on the same instance machine. In order to not have a single point of failure; using mod_proxy_balancer and modifying the VirtualHost would allow for managing multiple instances of the web apps behind the Apache proxy server.

sudo yum install mod_proxy_balancer

In the configuration file shown below, an additional instance of the hellomvc app is setup to run on port 5001. The Proxy section is set with a balancer configuration with two members to load balance byrequests.

<VirtualHost *:80>
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/?(.*) https://%{SERVER_NAME}/ [R,L]
</VirtualHost>

<VirtualHost *:443>
    ProxyPass / balancer://mycluster/ 

    ProxyPassReverse / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5001/

    <Proxy balancer://mycluster>
        BalancerMember http://127.0.0.1:5000
        BalancerMember http://127.0.0.1:5001 
        ProxySet lbmethod=byrequests
    </Proxy>

    <Location />
        SetHandler balancer
    </Location>
    ErrorLog /var/log/httpd/hellomvc-error.log
    CustomLog /var/log/httpd/hellomvc-access.log common
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
    SSLCertificateFile /etc/pki/tls/certs/localhost.crt
    SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>

Rate Limits

Using mod_ratelimit, which is included in the httpd module, the bandwidth of clients can be limited:

sudo nano /etc/httpd/conf.d/ratelimit.conf

The example file limits bandwidth as 600 KB/sec under the root location:

<IfModule mod_ratelimit.c>
    <Location />
        SetOutputFilter RATE_LIMIT
        SetEnv rate-limit 600
    </Location>
</IfModule>