Marx J. Moura

18 August 2019

Host ASP.NET Core on Linux with Nginx

.NET Core + Linux + Nginx

This is a step-by-step guide on how to configure Nginx on Ubuntu Server to run a single or multiple ASP.NET Core APIs on one domain (no subdomain).

This post is quite extensive so let's break into steps to achieve this:

  1. Install Nginx
  2. Configure firewall
  3. Configure Nginx
  4. Create a server block for the domain
  5. Configure ASP.NET Core API
    1. Configure the ASP.NET Core API as a service
    2. Configure a single ASP.NET Core API on the domain
    3. Configure multiple ASP.NET Core APIs on one domain

1. Install Nginx

First of all we need Nginx installed on the server. We can use apt-get to install.

sudo apt-get install nginx

After the installation the web server should already be up and running. To check the status of the Nginx run the following command:

sudo systemctl status nginx

2. Configure firewall

After install Nginx you may need to open HTTP or HTTPS ports in the firewall. If you are using Amazon EC2 (like me) just use the AWS Management Console to do this and you are ready to go to the next topic.

Let's list the application configurations that ufw knows how to work with:

sudo ufw app list

There are three profiles available for Nginx to open the ports 80 (HTTP) and port 443 (HTTPS):

Lets run the following command to enable the Nginx Full profile.

sudo ufw allow 'Nginx Full'

Now let's run the following command to verify the change:

sudo ufw status

3. Configure Nginx

The following configuration is what I'm using and should be enough to get started. In case you are interesting in details, the Nginx documentation is a good place for reference and also devdocs.io/nginx.

Create the /etc/nginx/proxy.conf configuration file with the content:

proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
large_client_header_buffers 4 16k;

Modify the file /etc/nginx/nginx.conf and replace the content with the following:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

include /etc/nginx/modules-enabled/*.config;

events {
  worker_connections 1024;
}

http {
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  types_hash_max_size 2048;
  keepalive_timeout 30;
  server_tokens off;

  include /etc/nginx/mime.types;
  include /etc/nginx/conf.d/*.config;
  include /etc/nginx/sites-enabled/*;
  include /etc/nginx/proxy.config;

  default_type application/octet-stream;

  log_format custom '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '"$http_x_forwarded_for" $request_id ';

  access_log /var/log/nginx/access.log custom;
  error_log  /var/log/nginx/error.log;
}

Edit the default server block to handle unmatched server_name by returning the status code of 444 (connection closed without response). Modify the default Nginx server block /etc/nginx/sites-available/default and replace the content with the following:

server {
  listen 80 default_server;
  listen [::]:80 default_server;

  return 444;
}

Test the Nginx configuration for correct syntax:

sudo nginx -t

If there are no errors, restart Nginx.

sudo systemctl restart nginx

4. Create a server block for the domain

First let's create a folder to the domain (e.g. example.com) within directory /var/www using the -p flag to create any necessary parent directories:

sudo mkdir -p /var/www/example.com

Next, to avoid any permission issues, make the Nginx user www-data owner of the directory that we just created:

sudo chown -R www-data: /var/www/example.com

Allow the owner and groups to write to the directory:

sudo chmod -R 775 /var/www/example.com

Add another user to the www-data group if you need to (e.g. ubuntu user of the Amazon EC2):

sudo usermod -a -G www-data ubuntu

Now let's create the server block (a configuration file) /etc/nginx/sites-available/example.com with the following content:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location / {
    return 200 "OK";
  }
}

To enable the new server block it is needed to create a symbolic link from the file in sites-available directory to the sites-enabled directory, which is read by Nginx during startup:

sudo ln -sfn /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Then test the configuration syntax and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

Now you should get the text "OK" when accessing http://example.com in your browser.

5. Configure ASP.NET Core API

Basically we need to configure the ASP.NET Core API as a service and configure the Nginx as a reverse proxy. The Microsoft documentation help me a lot.

During this process the following configuration files will be created or edited:

5.1. Configure the ASP.NET Core API as a service

Nginx isn't set up to manage the Kestrel process. Therefore, systemd can be used to create a service file to command (start, stop or restart) and monitor the API.

Create the service unit configuration file:

sudo vim /etc/systemd/system/my-example-api.service

The following is an example service configuration file for the My Example API (if necessary, change to suit your use case):

[Unit]
Description=My Example API

[Service]
WorkingDirectory=/var/www/example-api/current
ExecStart=/usr/bin/dotnet /var/www/example-api/current/ExampleAPI.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=my-example-api
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://localhost:5000
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
EnvironmentFile=/path/to/file.conf

[Install]
WantedBy=multi-user.target

Start the service and verify that it's running.

sudo systemctl start my-example-api.service
sudo systemctl status my-example-api.service

Once the API that is running on Kestrel is managed by systemd, all events and processes are logged to the journal (a systemd component). To view the my-example-api.service records, use the following command:

sudo journalctl -fu my-example-api.service

For filtering by time to reduce the amount of records returned:

sudo journalctl -fu my-example-api.service --since "2019-01-01" --until "2019-01-20 06:00"

5.2. Configure a single ASP.NET Core API on the domain

It is needed to configure Nginx as a reverse proxy to forward requests made to http://example.com on to the ASP.NET Core API running on Kestrel at http://localhost:5000. Modify the server block /etc/nginx/sites-available/example.com and replace the content with the following:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location / {
    proxy_pass http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }
}

Then test the configuration syntax and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

The API will be available at http://example.com.

5.3. Configure multiple ASP.NET Core APIs on one domain

We will configure Nginx as a reverse proxy to forward requests to multiple ASP.NET Core APIs by using the rewrite directive. Modify /etc/nginx/sites-available/example.com and replace the content with the following:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location /myapi1 {
    rewrite /myapi1/?(.*) /$1 break;
    proxy_pass http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }

  location /myapi2 {
    rewrite /myapi2/?(.*) /$1 break;
    proxy_pass http://localhost:5010;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }
}

You will need to have one service configuration file for each API to run both APIs as a service. Just repeat the step already described in this post paying attention to the paths and port used.

Then test the configuration syntax and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

Now both APIs myapi1 and myapi2 will be available on the same server at http://example.com/myapi1 and http://example.com/myapi2 respectively.

marxjmoura

I'm Marx J. Moura. In this blog I write about microservice architecture and web application development.