118

I'm used to using Apache with mod_proxy_html, and am trying to achieve something similar with NGINX. The specific use case is that I have an admin UI running in Tomcat on port 8080 on a server at the root context:

http://localhost:8080/

I need to surface this on port 80, but I have other contexts on the NGINX server running on this host, so want to try and access this at:

http://localhost:80/admin/

I was hoping that the following super simple server block would do it, but it doesn't quite:

server {
    listen  80;
    server_name screenly.local.akana.com;

    location /admin/ {
        proxy_pass http://localhost:8080/;
    }
}

The problem is that the returned content (html) contains URLs to scripts and style info that is all accessed at the root context, so I need to get these URLs rewritten to start with /admin/ instead of /.

How do I do this in NGINX?

Alexis Tyler
  • 1,394
  • 6
  • 30
  • 48
IanG
  • 1,459
  • 3
  • 13
  • 18

4 Answers4

184

We should first read the documentation on proxy_pass carefully and fully.

The URI passed to upstream server is determined based on whether "proxy_pass" directive is used with URI or not. Trailing slash in proxy_pass directive means that URI is present and equal to /. Absense of trailing slash means hat URI is absent.

Proxy_pass with URI:

location /some_dir/ {
    proxy_pass http://some_server/;
}

With the above, there's the following proxy:

http:// your_server/some_dir/ some_subdir/some_file ->
http:// some_server/          some_subdir/some_file

Basically, /some_dir/ gets replaced by / to change the request path from /some_dir/some_subdir/some_file to /some_subdir/some_file.

Proxy_pass without URI:

location /some_dir/ {
    proxy_pass http://some_server;
}

With the second (no trailing slash): the proxy goes like this:

http:// your_server /some_dir/some_subdir/some_file ->
http:// some_server /some_dir/some_subdir/some_file

Basically, the full original request path gets passed on without changes.


So, in your case, it seems you should just drop the trailing slash to get what you want.


Caveat

Note that automatic rewrite only works if you don't use variables in proxy_pass. If you use variables, you should do rewrite yourself:

location /some_dir/ {
  rewrite    /some_dir/(.*) /$1 break;
  proxy_pass $upstream_server;
}

There are other cases where rewrite wouldn't work, that's why reading documentation is a must.


Edit

Reading your question again, it seems I may have missed that you just want to edit the html output.

For that, you can use the sub_filter directive. Something like ...

location /admin/ {
    proxy_pass http://localhost:8080/;
    sub_filter "http://your_server/" "http://your_server/admin/";
    sub_filter_once off;
}

Basically, the string you want to replace and the replacement string

Vanuan
  • 31,770
  • 10
  • 98
  • 102
Dayo
  • 12,413
  • 5
  • 52
  • 67
  • 3
    Thanks, that helps a lot. I think sub_filter will do it. – IanG Sep 13 '15 at 00:40
  • 2
    I am curios to what extent nginx is already rewriting the output, woudln't it have to re write the host/hostname in links at a minimum? So eg, wouldn't you `sub_filter "http://localhost/" "http://localhost/admin/"` – ThorSummoner Feb 09 '17 at 18:38
  • 2
    To allow rewriting other than `text/html` mimetype, I had to add also `sub_filter_types *;`. – anttikoo Apr 11 '17 at 12:54
  • Something weird is happening for me with this solution. Resources (*.js, *.css etc are getting fetched), but the page fails to load. i would expect `http://your_server/admin/` to get resolved to `http://your_server` during the proxy_pass but it doesn't and i get an error `react-router /admin/ location did not match any routes` in my application because my application doesn't know anything about '/admin'. – Prachi Jun 13 '17 at 23:20
  • 1
    You may also need to add `proxy_redirect` directive so that the `Location` header sent by the response is also amended in accordance with the url. Check out this tutorial: https://www.cyberciti.biz/faq/proxy_redirect-change-replace-location-refresh-response-headers/ – vivanov May 05 '18 at 10:47
  • It doesn't look like the first example works as described. Actually `http://your_server/some_dir/some_subdir/some_file` will go to `http://some_server/`. Am I wrong? – Vanuan Jan 25 '19 at 13:12
  • Ok, the issue was that I'm using variables in `proxy_pass`. In those cases replacement doesn't work. – Vanuan Jan 25 '19 at 13:38
28

You may also need the following directive to be set before the first "sub_filter" for backend-servers with data compression:

proxy_set_header Accept-Encoding "";

Otherwise it may not work. For your example it will look like:

location /admin/ {
    proxy_pass http://localhost:8080/;
    proxy_set_header Accept-Encoding "";
    sub_filter "http://your_server/" "http://your_server/admin/";
    sub_filter_once off;
}
Magnus Reftel
  • 967
  • 6
  • 19
Vladimir Sh.
  • 385
  • 4
  • 6
5

You can use the following nginx configuration example:

upstream adminhost {
  server adminhostname:8080;
}

server {
  listen 80;

  location ~ ^/admin/(.*)$ {
    proxy_pass http://adminhost/$1$is_args$args;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $server_name;
  }
}
Alex Elkin
  • 574
  • 6
  • 11
  • 2
    Why is this downvoted? Any problem with the code? Seems like a nice and complex solution to me, solving some caveats of proxying an app that pop up later on. Not sure why `proxy_redirect off;` though. Also I'd add `proxy_set_header X-Forwarded-Proto $scheme;`. – LuH Oct 21 '20 at 09:46
  • 1
    > The problem is that the returned content (html) contains URLs to scripts and style info that is all accessed at the root context, so I need to get these URLs rewritten to start with /admin/ instead of /. At least at the time of posting this comment it doesn't fulfill this requirement. – Tobias Apr 22 '21 at 21:27
0

If there are no hyperlinks which need to be rewritten with sub_filter, you might just use the proxy_redirect directive:

location /admin/ {
    proxy_pass http://localhost:8080/;
    proxy_redirect / /admin/
}

It changes the Location-Header of the response according to the given 'match-rewrite' rule.

Lars Hadidi
  • 558
  • 1
  • 5
  • 15