Search code examples
dockergonginxreverse-proxynixos

Nginx Reverse Proxy on nixOS returns 404 when trying to load css/js


I've set up a nixOS to run an nginx as a reverse proxy for a docker-container. In the docker-container runs an golang-server which handles / with a function and also return files from the two folders static and js. It runs on port 8181. The code for this looks like this:

func main() {
        static := http.FileServer(http.Dir("static"))
        js := http.FileServer(http.Dir("js"))
        http.Handle("/static/", http.StripPrefix("/static/", static))
        http.Handle("/js/", http.StripPrefix("/js/", js))
        // Register function to "/"
        http.HandleFunc("/", indexHandler)
        fmt.Println("Server is starting...")
        err := http.ListenAndServe("0.0.0.0:8181", nil)
        if err != nil {
                log.Fatal("cannot listen and server", err)
        }
        
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
        wd, err := os.Getwd()
        var static = filepath.Join(wd, "static")
        var index = filepath.Join(static, "index.html")
        if err != nil {
                log.Fatal("cannot get working directory", err)
        }

        // Check if the request is an OPTIONS preflight request
        if r.Method == "OPTIONS" {
                // Respond with status OK to preflight requests
                w.WriteHeader(http.StatusOK)
                return
        }

        // POST-Request
        if r.Method == http.MethodPost {
              // do something
        } else {
                // Loading the index.html without any data
                tmpl, err := template.ParseFiles(index)
                err = tmpl.Execute(w, nil) // write response to w
                if err != nil {
                        http.Error(w, err.Error(), http.StatusInternalServerError)
                        log.Fatal("problem with parsing the index template ", err)
                }

        }
}

The structure of my app looks like this.

├── web
│   ├── Dockerfile
│   ├── go.mod
│   ├── server.go
│   └── static
│   │   ├── index.html
│   │   ├── style.css
│   │   └── table.html
│   └── js
│       └── htmx.min.js

The configuration of the nginx-section in the configuration.nix looks like this.

  services.nginx = {
    enable = true;
    recommendedGzipSettings = true;
    recommendedOptimisation = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;
    virtualHosts."myapp" = {
      sslCertificate = "/etc/ssl/nginx/ssl.cert";
      sslCertificateKey = "/etc/ssl/nginx/ssl.key";
      sslTrustedCertificate = "/etc/ssl/nginx/ssl.chain";
      forceSSL = true; # Redirect HTTP to HTTPS
      locations = {
        "/myapp" = { proxyPass = "http://localhost:8181/"; };
      };
      extraConfig = ''
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      '';
    };

  };

When I address the url https://server/myapp the index.html will load, but the style.css and the htmx.min.js will return a 404.

When addressing the container with the port 8181 instead of the url (http://server:8181), everything loads as usual.

I expect nginx to redirect requests for loading css and js to the container.

EDIT: The exact error is: GET https://server.name/static/style.css net::ERR_ABORTED 404 even though I am accessing https://server.name/myapp.

Another important information is that, I want run multiple containers this way over the same reverse proxy. So directing als css- or js-files to the same location won't work.


Solution

  • Please check the css/js references in the index.html file. It's very likely that you're referencing them by absolute paths like this:

    <html>
      <head>
        <link rel="stylesheet" type="text/css" href="/static/style.css" />
        <script src="/js/htmx.min.js"></script>
      </head>
    </html>
    

    A quick fix is to replace them with relative paths:

    <html>
      <head>
        <link rel="stylesheet" type="text/css" href="./static/style.css" />
        <script src="./js/htmx.min.js"></script>
      </head>
    </html>
    

    This approach is error-prone. A better approach is to modify the app to serve the content at /myapp:

    http.Handle("/myapp/static/", http.StripPrefix("/myapp/static/", static))
    http.Handle("/myapp/js/", http.StripPrefix("/myapp/js/", js))
    http.HandleFunc("/myapp/", indexHandler)
    

    And modify the reference paths in the index.html file:

    <html>
      <head>
        <link rel="stylesheet" type="text/css" href="/myapp/static/style.css" />
        <script src="/myapp/js/htmx.min.js"></script>
      </head>
    </html>
    

    And then modify the nginx configuration:

    locations = {
      "/myapp/" = { proxyPass = "http://localhost:8181/myapp/"; };
    };
    

    With this approach, no matter the reverse proxy is used or not, the web app will always be served at the uri /myapp/, which should make it easy to maintain.