nginxreverse-proxynginx-reverse-proxy

nginx reverse proxy - how to serve multiple apps


I am trying to build a reverse proxy with nginx to make all Is in my project reachable from single address. For a single service the configuration below works without problem

/etc/nginx/sites-enabled/reverse-proxy.conf

server {
        listen 80;
        listen [::]:80;
        location  / {

        resolver 127.0.0.1;
        allow "x.x.x.x";
        deny   all;
        proxy_pass http://consul:8500;
    }

}

So when I call server's ip x.x.x.x in my browser I see the Consul UI and the URL showing x.x.x.x/ui/dc1. Besides that, I see that the UI did requests for asset files successfully.

My question; is it possible two host different services on the same server and just reference to them with different location? For example, if I want to include Vault UI then I would think of doing something like this:

server {
        listen 80;
        listen [::]:80;
        location  /consul {

        resolver 127.0.0.1;
        allow "x.x.x.x";
        deny   all;
        proxy_pass http://consul:8500;
    }

        location  /vault {

        resolver 127.0.0.1;
        allow "x.x.x.x";
        deny   all;
        proxy_pass http://vault:8200;
    }

}

However I am not sure if this could be done this way. The farest I got, is to open the Consul UI with all other sub requests not found (i.e. loading assets).


UPDATE

I think my problem is that I am wrongly using location and proxy_pass

observing the first configuration (which is working)

server {
        listen 80;
        listen [::]:80;
        location  / {

        resolver 127.0.0.1;
        allow "x.x.x.x";
        deny   all;
        proxy_pass http://consul:8500;
    }

}

If I look at the curl command curl localhost -L -vvvv

*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 39
< Connection: keep-alive
< Location: /ui/
< 
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/ui/'
* Found bundle for host localhost: 0x557b754549e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /ui/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 7806
< Connection: keep-alive
< Accept-Ranges: bytes
< Last-Modified: Fri, 10 Jul 2020 07:37:44 GMT
< 
<!DOCTYPE html>
<html lang="en" class="ember-loading">
...

and I can see the html already. However, if I changed the conf file to this:

server {
        listen 80;
        listen [::]:80;
        location  /consul/ {

        resolver 127.0.0.1;
        allow "x.x.x.x";
        deny   all;
        proxy_pass http://consul:8500;
    }

}

and then try to call it like curl localhost/consul -L -vvvv, I get the following:

*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Type: text/html
< Content-Length: 178
< Location: http://localhost/consul/
< Connection: keep-alive
< 
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/consul/'
* Found bundle for host localhost: 0x55ba7959f9e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Length: 0
< Connection: keep-alive

I would appreciate any ideas on this issue


Solution

  • You are right, you are using location and proxy_pass a wrong way. When you use the

    location /vault {
        proxy_pass http://vault:8200;
    }
    

    construction, you are passing your URI to the upstream as-is, while most likely you want to strip the /vault prefix from it. To do it, you should use this one:

    location /vault/ {
        proxy_pass http://vault:8200/;
    }
    

    You can read more about the difference of the first and the second one here. However this still can prevent the assets from loading correctly.

    This question - how to proxy some webapp under some URI prefix - is being asked again and again on stackoverflow. The only right way to do it is to made your proxied app request its assets via relative URLs only (consider assets/script.js instead of /assets/script.js) or using the right prefix (/vault/assets/script.js). Some well-written apps are able to detect if they are used under such an URI prefix and use it when an asset link is being generated, some apps allows to specify it via some settings, but some are not suited for the such use at all. The reason why the webapp won't work without fulfilling these requirements is quite obvious - any URL not started with /vault won't match your location /vault/ { ... } block and would be served via main location block instead. So the best way to do it is to fix your webapp, however several workarounds can be used if you really cannot.

    Update @ 2022.02.19

    Related thread at the ServerFault: How to handle relative urls correctly with a nginx reverse proxy.

    Possible caveats using sub_filter on the JavaScript code: Nginx as reverse proxy to two nodejs app on the same domain.