nginx reverse proxy – how to serve multiple apps

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.

  • Some web frameworks already builds their webapps with relative URLs, but uses a <base href="/"> in the head section of index.html. For example, React or Angular use this approach. If you have such a line within your webapp root index.html, just change it to <base href="/vault/">.

  • Using conditional routing based on HTTP Referer header value. This approach works quite well for a single page applications for loading assets, but if a webapp contains several pages this approach won’t work, it’s logic for the right upstream detection would break after the first jump from one page to another. Here is an example:

    map $http_referer $prefix {
        ~https?://[^/]+/vault/     vault;
        # other webapps prefixes could be defined here
        # ...
        default                    base;
    }
    
    server {
    
        # listen port, server name and other global definitions here
        # ...
    
        location / {
            try_files "" @$prefix;
        }
        location /vault/ {
            # proxy request to the vault upstream, remove "/vault" part from the URI
            proxy_pass http://vault:8200/;
        }
        location @vault {
            # proxy request to the vault upstream, do not change the URI
            proxy_pass http://vault:8200;
        }
        location @base {
            # default "root" location
            proxy_pass http://consul:8500;
        }
    
    }
    

    Update @ 2022.02.19

    Here is one more possible approach using conditional rewrite:

    server {
    
        # listen port, server name and other global definitions here
        # ...
    
        if ($http_referer ~ https?://[^/]+/vault/)
            # rewrite request URI only if it isn't already started with '/vault' prefix
            rewrite ^((?!/vault).*) /vault$1;
        }
    
        # locations here
        # ...
    
    }
    
  • Rewriting the links inside the response body using sub_filter directive from ngx_http_sub_module. This is the ugliest one, but still can be used as the last available option. This approach has an obvious perfomance impact. Rewrite patterns should be determined from your upstream response body. Usually that type of configuration looked like

    location /vault/ {
        proxy_pass http://vault:8200/;
        sub_filter_types text/css application/javascript;
        sub_filter_once off;
        sub_filter 'href="/' 'href="/vault/';
        sub_filter "href="https://stackoverflow.com/" "href="http://stackoverflow.com/vault/";
        sub_filter 'src="/'  'src="/vault/';
        sub_filter "src="/"  "src="/vault/";
        sub_filter 'url("/'  'url("/vault/';
        sub_filter "url('/"  "url('/vault/";
        sub_filter "url(/"   "url(/vault/";
    }
    

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.

Leave a Comment