In this article, we would explore how to create a .NET 6 API and deploy it to a Linux VPS.
Introduction
Since the advent of .NET Core, it has been possible to use .NET on Linux and Mac for development purposes and deployment to Linux, apart from Windows.
I was working on a side project, in which the client didn’t have enough budget to procure cloud services, hence we went for a Linux VPS.
I would be creating a .NET 6 API on a Windows machine and would deploy it to a Linux VPS.
I procured the Linux VPS from The HostMe at an affordable rate for self-learning and development purposes. Do check out them.
API Creation
To create a .NET 6 API, one needs Visual Studio 2022.
The community edition works pretty fine for individual development purposes.
Let us open VS 2022 and start with the Web API template.

Choose the .NET 6 LTS version in the next screen.

The project gets created with the default WeatherForecastController.

Let us quickly execute the project and see it in action.

Let us add a static list of persons and create a PersonController to access it.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
public class Data
{
public static List<Person> GetPeople()
{
return new List<Person>
{
new Person
{
Id = 1,
Name ="John",
City="Chicago"
},
new Person
{
Id = 2,
Name ="Jay",
City="New York"
},
new Person
{
Id = 3,
Name ="Mary",
City="Washington"
},
};
}
}
PersonController.cs
namespace Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PersonController : ControllerBase
{
[HttpGet]
[Route("/persons")]
public IActionResult GetPeople()
{
return Ok(Data.GetPeople());
}
}
}
The “/persons” endpoint renders a static list of person class, as shown below in Swagger.

Now that the API is ready, let us configure the VPS to do the deployment.
.NET 6 SDK Installation on Ubuntu 20.04
Let us SSH to our VPS and check the version by using lsb_release -d command.

Prior to installing .NET 6 on the VPS, we need to add the Microsoft package signing key to our list of trusted keys and add the package repository.
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
Let us run the below command to install the .NET 6 SDK
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-6.0

Let us check whether .NET 6 SDK and runtime are installed properly or not.
We can execute the below commands to check.
dotnet --list-sdks
dotnet --list-runtimes

The official Microsoft documentation is very helpful in this case.
Prepping & Publishing the API
We need to do certain preps to our API, publish it, and copy it to a directory in the VPS.
We would not be accessing the API over HTTPS right now, hence we need to comment out the app.UseHttpsRedirection(); middleware in the Program.cs file.
Next, we would need to add Forwarded Headers Middleware, so that requests are forwarded by reverse proxy (we will use Nginx as a reverse proxy).
Forwarded Headers Middleware should be the first in the execution order of the middlewares.
.Net Core and above versions have Kestrel as a reverse proxy, but it is not as rich as IIS, Apache or Nginx. We can very well use either Apache or Nginx to be used in Linux. This article will use Nginx.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
Next, let us add the IP of the VPS to the list of known proxies to ensure that the request handling from the webserver to the internet is proper.
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("YOUR_VPS_IP_ADDRESS"));
});
Above are the preps which need to be done to the API.
Let us publish the API to a local directory.


The API gets published to a local directory as below.

Nginx Installation & Configuration
As pointed out earlier, we will use Nginx as the reverse proxy. Let us install and configure it.
The command to install Nginx is:
sudo apt-get install nginx
Let us start Nginx and see its status.
sudo service nginx start
sudo service nginx status

If everything is good till now, we would see the Nginx landing page by visiting http://SERVER_IP_ADDRESS/index.nginx-debian.html

We can also curl localhost in the terminal to check Nginx.

Now, let’s create a directory named as api to /var/www/ directory of the VPS.
We will need to copy the published API contents to the api directory created in the previous step. We can use scp command to do so, but I would use the WinSCP utility to do so, just because it provides a friendly GUI.

Let us now configure Nginx as a reverse proxy to route the requests to the .NET 6 API.
The below server config needs to be updated in the /etc/nginx/sites-enabled/default file.

server {
listen 80;
server_name _;
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_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Nginx will:
- listen on port 80 for all requests
- will route the requests to http://localhost:5000
Since we have changed the Nginx configuration, we need to test whether everything is okay.
sudo nginx -t

Let us restart Nginx:
sudo systemctl restart nginx
Let us try to curl localhost/persons or localhost/weatherforecast endpoints and see whether we are able to access the API.

We get a 502 bad gateway, let us troubleshoot it.
Do we have any service running at port 5000?
netstat -tlp | grep 5000
Nothing seems to be running on port 5000.

Let us see the Nginx logs.
We get connection refused error in the Nginx logs.

The problem seems self-explanatory. There is nothing running on port 5000, and hence Nginx is not able to forward requests to it.
Open another PowerShell window and start the API manually. Browse to the api directory and run dotnet Api.dll

Try to access the API endpoint in the first Powershell window as below. The resulting json collections should be returned.

API endpoint will be accessible from the browser as well –
http://SERVER_IP_ADDRESS/persons or http://SERVER_IP_ADDRESS/weatherforecast


So, we are able to access our endpoints properly, however, there is a slight problem. We have executed our API manually on the server by browsing to the api directory and running dotnet Api.dll. If we close the Powershell window the service will stop.
Let us create a service in which we can run our API automatically as a daemon.
We would create a service under the directory /etc/systemd/system/. Let’s name it as persons.service and put the content as below.
[Unit]
Description= Sample Persons .NET 6 API running on Ubuntu 20.04
[Service]
WorkingDirectory=/var/www/api
ExecStart=/usr/bin/dotnet /var/www/api/Api.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=personsapi-identifier
User=www-data
Environment=ASPNETCORE\_ENVIRONMENT=Development
Environment=DOTNET\_PRINT\_TELEMETRY\_MESSAGE=false
[Install]
WantedBy=multi-user.target
Let us check the status of the created service as below:
systemctl status persons.service

Let us start the service, because it seems inactive, and again check the status.
sudo systemctl start persons.service

The service is now active and running.
Let us browse to our endpoints, they should be accessible over http.


Code
Code is committed to Github, Program.cs is where all the magic happens.
https://github.com/anurag1302/.net6-linux-api
Troubleshooting Tips
Couple of times I have seen error that port 5000 is already in use.
To overcome this we can make use of any other random port (5200) as below.
var app = builder.Build();
builder.WebHost.UseUrls("http://*:5200");
This 5200 port also should have proper permissions in the ufw allow list.
Conclusion
The article ends over here.
We will install SQL Server on the VPS in the next article, and create an endpoint which talks to the database and returns a bunch of json objects.
Please share the article.
Comments and criticisms are welcomed.