NGINX is giving 404 errors on all but the HTML pages

I setup nginx as a reverse proxy in a docker container to link to sites outside of the container. I have a vhost config setup as follows (I tried adding the ^~ before the location for home-assistant):

server { # simple reverse-proxy
    listen       80;

    location / {
        proxy_pass http://192.168.1.99:6789;
    }

    location ^~ /home-assistant {
        proxy_pass http://192.168.1.99:8123/;
    }

    location /calibre-web/ {
        proxy_pass http://192.168.1.99:8181/;
    }

  }

For both home-assistant and calibre-web sites, the page loads, but I get 404 errors for all other items (images, CSS, etc...). If I try to click a link in the applications, it links me to 192.168.1.99/file and not 192.168.1.99/site-folder/file as it should. Below are a few records from the logs (note the one 200 response, and the other 404 responses). What am I doing wrong here? Thanks in advance for the help.

192.168.1.6 - randy [15/Aug/2016:03:15:42 +0000] "GET /frontend/panels/dev-template-0a099d4589636ed3038a3e9f020468a7.html HTTP/1.1" 404 199 "http://192.168.1.99/home-assistant/" "Mozilla/5.0 (X11; CrOS x86_64 8350.60.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.85 Safari/537.36"

192.168.1.6 - randy [15/Aug/2016:03:15:42 +0000] "GET /frontend/panels/logbook-66108d82763359a218c9695f0553de40.html HTTP/1.1" 404 199 "http://192.168.1.99/home-assistant/" "Mozilla/5.0 (X11; CrOS x86_64 8350.60.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.85 Safari/537.36"

192.168.1.6 - randy [15/Aug/2016:03:17:54 +0000] "GET /home-assistant/ HTTP/1.1" 200 1654 "-" "Mozilla/5.0 (X11; CrOS x86_64 8350.60.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.85 Safari/537.36"

192.168.1.6 - randy [15/Aug/2016:03:17:55 +0000] "GET /static/icons/favicon-192x192.png HTTP/1.1" 404 91 "http://192.168.1.99/home-assistant/" "Mozilla/5.0 (X11; CrOS x86_64 8350.60.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.85 Safari/537.36"

192.168.1.6 - randy [15/Aug/2016:03:17:55 +0000] "GET /static/core-457d5acd123e7dc38947c07984b3a5e8.js HTTP/1.1" 404 91 "http://192.168.1.99/home-assistant/" "Mozilla/5.0 (X11; CrOS x86_64 8350.60.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.85 Safari/537.36"

Here is a copy of my root nginx.conf. I'm guessing the error is in here somewhere, I just cant figure out where.


Solution 1:

You are trying to proxy_pass requests from a location that is unknown to the original application. In docker container application still believes that it is accessed from the website root (http://192.168.1.99:8123/), so it generates all URLs relative to /, not to your /home-assistant/ or /calibre-web/.

There are numerous ways to solve this problem, you should choose whatever works best for you:

1. Replacing links in proxied html

First of all it is replacing every link received from the proxied application before submitting result to the client is possible with ngx_http_sub_module. It's not enabled by default, so you may have to compile nginx from source if your distro doesn't have it (to check availability of this module run nginx -V, which should display --with-http_sub_module somewhere along configuration parameters).

If you choose this method, add the following directives to your config:

location ^~ /home-assistant {
    rewrite ^/home-assistant(/.*)$ $1 break;
    proxy_pass http://192.168.1.99:8123/;
    sub_filter "<head>" "<head><base href=\"${scheme}://${host}/home-assistant\">";
}

2. Changing application path inside the container

Moving application inside docker container to the /home-assistant instead of root, so it would generate all URLs in served content related to that path instead of root /. This is an obvious (and probably the easiest) solution, which doesn't require touching nginx configs at all (instead you edit configuration inside docker)

3. Choosing upstream based on the Referer header

Determining which upstream should be used based on the Referer header. Taken from here. Seems pretty smart, though I wouldn't use it in production myself (note it only works for css/js files).

location ~* ^/(css|js)/.+\.(css|js)$ {
    #checking if referer is from home-assistant
    if ($http_referer ~ "^.*/home-assistant"){
        return 417;
    }

    #checking if referer is from calibre-web
    if ($http_referer ~ "^.*/calibre-web"){
        return 418;
    }
}

error_page   417  /home-assistant$request_uri;
error_page   418  /calibre-web$request_uri;

location ^~ /home-assistant {
    proxy_pass http://192.168.1.99:8123/;
}

location /calibre-web {
    proxy_pass http://192.168.1.99:8181/;
}

If I were you, I'd go with #2 - moving apps inside the container as the easiest & the least error-prone way to get what you need.

P.S. I completely forgot that you can serve static files directly from your hard drive, since your backend is on the same server as nginx. It will require some application-dependant configuration, but as a result you'll get most efficient solution..

Solution 2:

One solution might be not to pass an URI in the proxy_pass directive:

location ^~ /home-assistant {
    proxy_pass http://192.168.1.99:8123;
}

location ^~ /calibre-web/ {
    proxy_pass http://192.168.1.99:8181;
}

Notice the missing / character after port number in proxy_pass statement. That means that nginx will pass the URI sent to the server to the app server.

With the / character (URI), it will replace the request URI with that.

This behavior is documented at http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass