Hosting an ASP.NET Core 2 application on a Raspberry Pi

As you probably know, .NET Core runs on many platforms: Windows, macOS, and many UNIX/Linux variants, whether on x86/x64 architectures or on ARM. This enables a wide range of interesting scenarios… For instance, is a very small machine like a Raspberry Pi, which its low performance ARM processor and small amount of RAM (1 GB on my RPi 2 Model B), enough to host an ASP.NET Core web app? Yes it is! At least as long as you don’t expect it to handle a very heavy load. So let’s see in practice how to deploy an expose an ASP.NET Core web app on a Raspberry Pi.

Creating the app

Let’s start from a basic ASP.NET Core 2.0 MVC app template:

dotnet new mvc

You don’t even need to open the project for now, just compile it as is and publish it for the Raspberry Pi:

dotnet publish -c Release -r linux-arm


We’re going to use a Raspberry Pi running Raspbian, the official Linux distro for Raspberry Pi, which is based on Debian. To run a .NET Core 2.0 app, you’ll need version Jessie or higher (I used Raspbian Stretch Lite). Update: as Tomasz mentioned in the comments, you also need a Raspberry Pi 2 or more recent, with an ARMv7 processor; The first RPi has an ARMv6 processor and cannot run .NET Core.

Even though the app is self-contained and doesn’t require .NET Core to be installed on the RPi, you will still need a few low-level dependencies; they are listed here. You can install them using apt-get:

sudo apt-get update
sudo apt-get install curl libunwind8 gettext apt-transport-https

Deploy and run the application

Copy all files from the bin\Release\netcoreapp2.0\linux-arm\publish directory to the Raspberry Pi, and make the binary executable (replace MyWebApp with the name of your app):

chmod 755 ./MyWebApp

Run the app:


If nothing went wrong, the app should start listening on port 5000. But since it listens only on localhost, it’s only accessible from the Raspberry Pi itself…

Exposing the app on the network

There are several ways to fix that. The easiest is to set the ASPNETCORE_URLS environment variable to a value like http://*:5000/, in order to listen on all addresses. But if you intend to expose the app on the Internet, it might not be a good idea: the Kestrel server used by ASP.NET Core isn’t designed to be exposed directly to the outside world, and isn’t well protected against attacks. It is strongly recommended to put it behind a reverse proxy, such as nginx. Let’s see how to do that.

First, you need to install nginx if it’s not already there, using this command:

sudo apt-get install nginx

And start it like this:

sudo service nginx start

Now you need to configure it so that requests arriving to port 80 are passed to your app on port 5000. To do that, open the /etc/nginx/sites-available/default file in your favorite editor (I use vim because my RPi has no graphical environment). The default configuration defines only one server, listening on port 80. Under this server, look for the section starting with location /: this is the configuration for the root path on this server. Replace it with the following configuration:

location / {
        proxy_pass http://localhost:5000/;
        proxy_http_version 1.1;
        proxy_set_header Connection keep-alive;

Be careful to include the final slash in the destination URL.

This configuration is intentionnally minimal, we’ll expand it a bit later.

Once you’re done editing the file, tell nginx to reload its configuration:

sudo nginx -s reload

From your PC, try to access the app on the Raspberry Pi by entering its IP address in your browser. If you did everything right, you should see the familiar home page from the ASP.NET Core app template!

Note that you’ll need to be patient: the first time the home page is loaded, its Razor view is compiled, which can take a while on the RPi’s low-end hardware. ASP.NET Core 2.0 doesn’t support precompilation of Razor views for self-contained apps; this is fixed in 2.1, which is currently in preview. So for now you have 3 options:

  • be patient and endure the delay on first page load
  • migrate to ASP.NET Core 2.1 preview, as explained here
  • make a non self-contained deployment, which requires .NET Core to be installed on the RPi

For this article, I chose the first options to keep things simple.

Proxy headers

At this point, we could just leave the app alone and call it a day. However, if your app is going to evolve into something more useful, there are a few things that aren’t going to work correctly in the current state. The problem is that the app isn’t aware that it’s behind a reverse proxy; as far as it knows, it’s only listening to requests on localhost on port 5000. Which means it cannot know:

  • the actual client IP (requests seem to come from localhost)
  • the protocol scheme used by the client
  • the actual host name specified by the client

For the app to know these things, it has to be told by the reverse proxy. Let’s change the nginx configuration so that it adds a few headers to incoming requests. These headers are not standard, but they’re widely used by proxy servers.

    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host   $http_host;
    proxy_set_header X-Forwarded-Proto  http;

X-Forwarded-For contains the client IP address, and optionally the addresses of proxies along the way. X-Forwarded-Host contains the host name initially specified by the client, and X-Forwarded-Proto contains the original protocol scheme (hard-coded to http here since HTTPS is not configured).

(Don’t forget to reload the nginx configuration)

We also need to change the ASP.NET Core app so that it takes these headers into account. This can be done easily using the ForwardedHeaders middleware; add this code at the start of the Startup.Configure method:

app.UseForwardedHeaders(new ForwardedHeadersOptions
    ForwardedHeaders = ForwardedHeaders.All

In case you’re wondering what a middleware is, this article might help!

This middleware will read the X-Forwarded-* headers from incoming requests, and use them to modify:

  • the Host and Scheme of the request
  • the Connection.RemoteIpAddress, which contains the client IP.

This way, the app will behave as if the request was received directly from the client.

Expose the app on a specific path

Our app is now accessible at the URL http://<ip-address>/, i.e. at the root of the server. But if we want to host several applications on the Raspberry Pi, it’s going to be a problem… We could put each app on a different port, but it’s not very convenient. It would be better to have each app on its own path, e.g. with URLs like http://<ip-address>/MyWebApp/.

It’s pretty easy to do with nginx. Edit the nginx configuration again, and replace location / with location /MyWebApp/ (note the final slash, it’s important). Reload the configuration, and try to access the app at its new URL… The home page loads, but the CSS and JS scripts don’t: error 404. In addition, links to other pages are now incorrect, and point to http://<ip-address>/Something instead of http://<ip-address>/MyWebApp/Something. What’s going on?

The reason is quite simple: the app isn’t aware that it’s not served from the root of the server, and generates all its links as if it were… To fix this, we can ask nginx to pass yet another header to our app:

proxy_set_header X-Forwarded-Path   /MyWebApp;

Note that this X-Forwarded-Path header is even less standard than the other ones, since I just made it up… So of course, there’s no built-in ASP.NET Core middleware that can handle it, and we’ll need to do it ourselves. Fortunately it’s pretty easy: we just need to use the path specified in the header as the path base. In the Startup.Configure method, add this after the UseForwardHeaders statement:

// Patch path base with forwarded path
app.Use(async (context, next) =>
    var forwardedPath = context.Request.Headers["X-Forwarded-Path"].FirstOrDefault();
    if (!string.IsNullOrEmpty(forwardedPath))
        context.Request.PathBase = forwardedPath;

    await next();

Redeploy and restart the app, reload the nginx configuration, and try again: now it works!

Run the app as a service

If we want our app to be always running, restarting it manually every time it crashes or when the Raspberry Pi reboots isn’t going to be sustainable… What we want is to run it as a service, so that it starts when the system starts, and is automatically restarted if it stops working. To do this, we’ll take advantage of systemd, which manages services in most Linux distros, including Raspbian.

To create a systemd service, create a MyWebApp.service file in the /lib/systemd/system/ directory, with the following content:

Description=My ASP.NET Core Web App



(replace the name and paths to match your app of course)

Enable the service like this:

sudo systemctl enable MyWebApp

And start it like this (new services aren’t started automatically):

sudo systemctl start MyWebApp

And that’s it, your app is now monitored by systemd, which will take care of starting or restarting it as needed.


As you can see, running an ASP.NET Core 2.0 app on a Raspberry Pi is not only possible, but reasonably easy too; you just need a bit of fiddling with headers and reverse proxy settings. You won’t host the next Facebook or StackOverflow on your RPi, but it’s fine for small utility applications. Just give free rein to your imagination!

31 thoughts on “Hosting an ASP.NET Core 2 application on a Raspberry Pi”

  1. Hi Thomas,

    Thanks for writing the article! I got quite far with it, but I feel I’m 1 step away from a working ASP.NET Core site:
    I went through the section ‘Exposing the app on the network’ but instead of getting the default (ASP) website, I get a 502 Bad Gateway.

    Could you give me a nudge into the right direction? Thanks so far!

    1. Hi Dennis,

      This error usually means the proxy cannot connect to the website.
      Make sure that
      1. the ASP.NET Core app is running
      2. nginx is forwarding the requests to the same URL on which the app is listening

      You may also need to reload the nginx configuration with sudo nginx -s reload

      1. Hi Thomas,

        Thanks for the reply! I went over it again but still cannot figure out where it goes wrong:
        1. I make sure both nginx and the app are running on the Pi. Making sure to reload.
        2. As specified, I modify the nginx config file /etc/nginx/sites-available/default to listen to port 5000.
        This still causes the 502 Bad Gateway to pop up.
        Could it be that I’m missing a configuration on my router’s port forwarding, or would I need to look into the WebbApp settings itself?


        1. I don’t think it has anything to do with the app settings, as long as it’s listening on the correct port.

          2. As specified, I modify the nginx config file /etc/nginx/sites-available/default to listen to port 5000.

          It’s your app that should be listening on port 5000, not nginx. nginx should listen on port 80

          1. Found it! Apparently putting projects in my /home/pi/projects/ folder is not a location nginx seems to be aware of: I had projects running there and got the 502 error.
            Now I put a project in the /var/www/ directory, started up the app, and it worked!

            Thanks Thomas 🙂

  2. Very useful tutorial, thank you!
    One small remark on starting the application as a service: the file MyWebApp.service must be located in /etc/systemd/system.

    1. Hi Nicko, I just checked on my RPi, the file is in /lib/systemd/system, as indicated in the article. To be honest, I don’t know Linux very well, so I don’t know the difference.

  3. Thank you very much for this great tutorial!
    Works great with .net core 2.1 and Raspberry Pi 3+. Very impressive how easy a .net web application can be run on the RasPi.

  4. Great article, thanks a lot!

    For .NET Core 2.1 I had to add

    using Microsoft.AspNetCore.HttpOverrides;

    to Startup.cs to get the Forwarding running.

  5. I notice you’re using this line in the [Unit] section of your MyWebApp.service file:


    Since I won’t be exposing my service to the network, will I be able to omit this line?

  6. Great article, thank you very much !

    I did all the procedure until Proxy Header and have been able to publish it on my local network.

    But I am having two issues :

    First the ASP.NET webpage is not correctly showing. It missing the ribbon at the top, about and contact tabs are not working.

    Second, after starting my APP in linux, it show a warn :
    Failed to determine the https port for redirect.

      1. I’m probably a little late to the game, but I’m guessing it could be a Bootstrap version issue that’s causing the problems with the UI.

  7. Hello there! I have a self-contained web app and a PostgreSQL database on my Raspberry up and running. The thing is i want to add the initial migration and update the database on the Pi, in order to populate the Identity Tables like ASPnetUsers etc.. so i can register/login.
    Do you know how i could achieve this? THanks!

  8. Hi Thomas, Your blog has been very helpful for a beginner like me. I was able to follow your steps and host one app. I would like to host more than one app on my RPI. So i believe i should add another location /WebApp2/ in the default file but what should proxy_pass look like in location /WebApp2/ ? Should it still be http://localhost:5000/? I guess not?

    Also, is there a way to use hostname instead of ip address on the url from client?

    1. Hi Varun,

      Yes, you should configure a default location for nginx. proxy_pass should refer to the URI where your application is listening. So, not on port 5000 if you already have an application listening on that port, of course; you should configure the app to listen on a different port.

      As for using a hostname, you can just point a DNS record to an address where your RPi is reachable. If you want to route to different apps based on different hostnames, you’ll need to configure nginx accordingly; not sure how to do that exactly, you’ll have to read the docs 😉

  9. When I run the WebApp from console manually on RPI then it works without any issues.
    But when I start it as a service it doesn’t work:

    Feb 02 14:07:39 raspberrypi systemd[1]: Started BugsIncluded .NET Core Web App.
    Feb 02 14:07:39 raspberrypi systemd[1]: bugsincluded.service: Main process exited, code=exited, status=127/n/a
    Feb 02 14:07:39 raspberrypi systemd[1]: bugsincluded.service: Unit entered failed state.
    Feb 02 14:07:39 raspberrypi systemd[1]: bugsincluded.service: Failed with result ‘exit-code’.
    Feb 02 14:07:59 raspberrypi systemd[1]: bugsincluded.service: Service hold-off time over, scheduling restart.

    Thomas, do you know how to fix this?

  10. I can start the website and it runs on localhost:5000
    then I do nginx but when I surf to my local Ipadress I have an error. Error is “ERR_CONNECTION_REFUSED”.
    Maybe you can gave me your full settings of nginx? with everything of the server in it?

Leave a Reply

Your email address will not be published. Required fields are marked *