Hosting Multiple .NET Web Apps on Linux (using Nginx): Static Files throw a HTTP 404

koeks525

Executive Member
Joined
Jul 14, 2012
Messages
6,016
Reaction score
1,199
Location
Canada
Hi everyone,

Firstly I must appologize for duplicating the same question here. I have been trying to figure out what else I should look at, or what I could be doing wrong when trying to host multiple .net web apps on linux. Each web app has its own subdirectory where the static files (kept inside the wwwroot folder) are contained (css and js related files). When running the application, the web apps do run successfully however the css and js files don't load - these throw a 404. What is interesting is that it looks like nginx is trying to get the static files from the root directory when these files are not there (they are within the wwwroot of each subdirectory). I am not sure how I can solve this issue. I have .NET 7 installed on my Linux Ubuntu VPS (22.04). This is what my nginx server block config file looks like:

NGINX:
server {
        server_name subdomain.domain.co.za www.subdomain.domain.co.za;
        error_log /var/apps/appname/logs/appname.log;
        root /var/www/subdomain.domain.co.za;

        location /proxy/ {
         proxy_pass         http://localhost:8886/;
         proxy_http_version 1.1;
         proxy_set_header   Upgrade $http_upgrade;
         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;
        }

        location /auth/ {
         proxy_pass         http://localhost:8888/;
         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;   
        }
    
        location /api/ {
         proxy_pass         http://localhost:8887/;
         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;   
        }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/subdomain.domain.co.za/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/subdomain.domain.co.za/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}server {
    if ($host = www.subdomain.domain.co.za) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = subdomain.domain.co.za) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        
        server_name subdomain.domain.co.za www.subdomain.domain.co.za;
    listen 80;
    return 404; # managed by Certbot




}

For the auth app (the one which I had been using to try figure out what is going wrong), I have tried being explicit about where to find the static files, as well as what the base path is. This hasn't worked. I added these snippets to my Program.cs file:

C#:
app.UseStaticFiles();
if (!app.Environment.IsDevelopment())
{
    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider("/var/www/subdomain.domain.co.za/auth/wwwroot"),
        RequestPath = new PathString("/auth/wwwroot")
    });
    app.UsePathBase("/auth");
}

Not sure if this is any relevant, but my appsettings.json file does have a section for Kestrel config where I state what port should be used:

JSON:
{
...,
"Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:8888/"
      }
    }
  }
}

I had a look at the logs. When I have the root defined in the nginx config file, it looks like the server is trying to get the css/js files from the root directory, which is strange because the static files are contained within each subdirectory for each app.

Code:
2023/06/17 09:22:08 [error] 800495#800495: *6 open() "/var/www/subdomain.domain.co.za/css/site.css" failed (2: No such file or directory), client: XXX.XXX.XXX.XXX, server: subdomain.domain.co.za, request: "GET /css/site.css HTTP/1.1", host: "subdomain.domain.co.za", referrer: "https://subdomain.domain.co.za/auth/"

Now, when I remove the root definition, it seems like nginx tries to find the css/js files from another location (im guessing a default location):

Code:
2023/06/17 09:18:34 [error] 799822#799822: *6 open() "/usr/share/nginx/html/lib/jquery/dist/jquery.min.js" failed (2: No such file or directory), client: XXX.XXX.XXX.XXX, server: subdomain.domain.co.za, request: "GET /lib/jquery/dist/jquery.min.js HTTP/1.1", host: "subdomain.domain.co.za", referrer: "https://sudomain.domain.co.za/auth/"

Anyone here has ideas on what I could be doing wrong, or what else I could try? To launch the app, I do have a service file defined... it looks like everything looks fine (the working directory seems to be defined correctly):

Code:
[Unit]
Description=AppName Authorization Server

[Service]
WorkingDirectory=/var/www/subdomain.domain.co.za/auth
ExecStart=/usr/bin/dotnet /var/www/subdomain.domain.co.za/auth/AppName.AuthorizationServer.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-AppName-auth
User=user
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target
 
Just had a thought now... if I cannot get past this issue, I could just host the apps as separate apps (as opposed to creating subdirectories for each app). This would mean all apps would have their own root directory and their own server config files. Because all files would be hosted in the related app's root directory, I wouldn't run into this issue.
 
I assume this all works locally?

Are you running in “Development” when you deploy?

You should not need base path at all.
We deploy loads of .NET 6/7 apps to run not in the root context, .e.g. /app1, /app2, etc, and they are all serving out their own wwwroot path

But it’s likely you are running/building in “Release”

Edit: oh that says “if it’s not development”.
You definitely don’t want/need to set the wwwroot or base context path


What URL is the browser calling, for say one of the CSS files?
 
Top
Sign up to the MyBroadband newsletter
X