nginx 'proxy_pass' cannot have URI part in location? - nginx

I have a location block as
location #test{
proxy_pass http://localhost:5000/1;
}
but nginx complains that "proxy_pass cannot have URI part in location given by regular expression..." Does anyone know what might be wrong?
I'm trying to query localhost:5000/1 when an upload is complete:
location /upload_attachment {
upload_pass #test;
upload_store /tmp;
...
}

Technically just adding the URI should work, because it's documented here and it says that it should work, so
location #test{
proxy_pass http://localhost:5000/1/; # with a trailing slash
}
Should have worked fine, but since you said it didn't I suggested the other way around, the trick is that instead of passing /my/uri to localhost:5000/1, we pass /1/my/uri to localhost:5000,
That's what my rewrite did
rewrite ^ /1$1
Meaning rewrite the whole URL, prepend it with /1 then add the remaining, the whole block becomes
location #test{
rewrite ^ /1$1;
proxy_pass http://localhost:5000;
}
Note: #Fleshgrinder provided an answer explaining why the first method didn't work.

What's actually happening here?
nginx cannot process your desired URI part in the proxy_pass directive because you're within a named location (hence the error message). This is because nginx is built in a modular fashion and each configuration block is read in various stages by the various modules. So just remember that you cannot have a URI within your proxy_pass directive in the following cases:
Regular Expression Locations
Named Locations
if Blocks
How could we solve this problem?
Mohammad AbuShady explained how to do a rewrite and pass the requested URI to the proxy server. I just wanted to clarify the reason.

Try omitting the "/" (URI part) and check.
location #test{
proxy_pass http://localhost:5000;
}

Related

How does Nginx decide which location to handle requests if multiple locations match

I have nginx server with two locations:
location /service1 {
rewrite ^/service1/?(.*)$ /$1 break;
proxy_pass http://localhost:xxxx;
}
location ~* /service2(?<stuff>.*)$ {
rewrite ^ /service2$stuff break;
proxy_pass http://192.168.0.X;
}
Let's say I have a request http://hostname/service1/service2. I notice service2 always handle the request. But I want service 1 to handle it. How to set it?
It is important to understand how Nginx chooses which location block to handle requests. Please read https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms, very helpful article.
Relevant excerpt from the article
It is important to understand that, by default, Nginx will serve regular expression matches in preference to prefix matches. However, it evaluates prefix locations first, allowing for the administer to override this tendency by specifying locations using the = and ^~ modifiers.
In your case you could use ‘location ^~ /service1’
Nginx location matcher - https://nginx.viraptor.info/

Reverse image proxy without specifying host

I have the following in my config as a reverse proxy for images:
location ~ ^/image/(.+) {
proxy_pass http://example.com/$1;
}
The problem is that not all images will be example.com images and so we need to pass in the full url. If I try:
location ~ ^/image/(.+) {
proxy_pass $1;
}
I get an error:
invalid URL prefix in "https:/somethingelse.com/someimage.png"
The question is quite vague, but, based on the error message, what you're trying to do is perform a proxy_pass entirely based on the user input, by using the complete URL specified after the /image/ prefix of the URI.
Basically, this is a very bad idea, as you're opening yourself to become an open proxy. However, the reason it doesn't work as in the conf you supplied is due to URL normalisation, which, in your case, compacts http://example into http:/example (double slash becomes single), which is different in the context of proxy_pass.
If you don't care about security, you can just change merge_slashes from the default of on to off:
merge_slashes off;
location …
Another possibility is to somewhat related to nginx proxy_pass and URL decoding
location ~ ^/image/.+ {
rewrite ^ $request_uri;
rewrite ^/image/(.*) $1 break;
return 400;
proxy_pass $uri; # will result in an open-proxy, don't try at home
}
The proper solution would be to implement a whitelist, possibly with the help of map or even prefix-based location directives:
location ~ ^/image/(http):/(upload.example.org)/(.*) {
proxy_pass $1://$2/$3;
}
Do note that, as per the explanation in the begginning, the location above is subject to the merge_slash setting, so, it'll never have the double // by default, hence the need to add the double // manually at the proxy_pass stage.
I would use a map in this case
map $request_uri $proxied_url {
# if you don't care about domain and file extension
~*/image/(https?)://?(.*) $1://$2;
# if you want to limit file extension
~*/image/(https?)://?(.*\.(png|jpg|jpeg|ico))$ $1://$2;
# if you want to limit file extension and domain
~*/image/(https?)://?(abc\.xyz\.com/)(.*\.(png|jpg|jpeg|ico))$ $1://$2$3;
default "/404";
}
Then in your proxy pass part you would use something like below
location /image/ {
proxy_pass $proxied_url;
}
I have given three different example depending how you want to handle it

Nginx Rewrite with Regex Multiple Token (+)

I'm having some issues with rewriting when it comes to multiple token numbers.
I'm working on versioning our API so deprecated version calls will go to our latest version. For the sake of simplicity, let's say we are supporting v4 and v5. When some hits our v1-3, we need it to go to v5. Likewise if someone hits v6, it should go to v5 as well.
Our nginx.conf uses proxy_pass with upstream so I have each version running on a different port
upstream v4 {
server 127.0.0.1:3000
}
upstream v5 {
server 127.0.0.1:3001
}
then we use location blocks to proxy
location ^~ /v5 {
proxy_pass $scheme://v5;
}
location ^~ /v4 {
proxy_pass $scheme://v4;
}
location ~* "^/v[0-9]+" {
rewrite ^/v[0-9]+/(.*)$ /$latestVersion/$1;
proxy_pass $scheme://$latestUpstream;
}
The last location block works for v0-9 but fails to catch when the number is multiple digits like v11, even though I'm using the +.
Any help on this would be great. Really confused by this. Thanks!
In the following block:
location ~* "^/v[0-9]+" {
rewrite ^/v[0-9]+/(.*)$ /$latestVersion/$1;
proxy_pass $scheme://$latestUpstream;
}
The rewrite statement performs an implicit rewrite ... last, which means that the URI is eventually processed by the location ^~ /v5 block, rendering the proxy_pass statement in this block, almost redundant. See this document for rewrite syntax.
The rewrite fails to match when the URI is presented as the version string alone without a trailing /, e.g. /v11.
You need to make the / following the version string optional. Try:
location ^~ /v {
rewrite ^/v[0-9]+(/.*)$ /$latestVersion$1 last;
}
The regular expression location block is probably unnecessary, so I have substituted a shorter prefix location (see this document for location syntax).
I have added a last suffix to make the intent of the rewrite clear.
EDIT: Responding to your comment
Distinguishing between the URIs /v5 and /v51 (i.e. without the trailing /) is problematic using prefix location blocks alone. If the URI always had (at minimum) a trailing / after the version string, it would be easy to solve by simply adding a trailing / to your two existing location blocks.
But supposing you need the short URIs like /v5 and /v51 to work, you should switch the solution to regular expression location blocks.
Be aware that regular expression location blocks are evaluated differently, and that order is important. You may need to move these location blocks before other regular expression location blocks in order to prevent them from being overridden by other code in your configuration file.
For example:
location ~* ^/v5(/|$) {
proxy_pass $scheme://v5;
}
location ~* ^/v4(/|$) {
proxy_pass $scheme://v4;
}
location ~* ^/v[0-9] {
rewrite ^/v[0-9]+(/.*)$ /$latestVersion$1 last;
}
Notice that the modifier is changed to ~* which introduces a regular expression (and also makes it case insensitive - which may or may not be important to you).

proxy_pass in nginx to publish webapp under a different directory

I have this location element:
location ~* ^/publicapp {
proxy_pass https://myserver.domain.local;
}
The server myserver.domain.local hosts a web application located under /myapp.
I want to make it publicly available via https://www.mywebsite.com/publicapp. How do I tell nginx to translate /myapp to /publicapp?
Please keep in mind that I use ~* to allow case-insensitivity. Thus, I cannot use a URI with proxy_pass.
Kind regards,
Kevin
Try this:
location ~* /publicapp/ {
rewrite ^/publicapp/(.*)$ /myapp/$1 break;
proxy_pass https://myserver.domain.local;
}
This will rewrite your path and use new one at the .local server.
It works using
rewrite ^/publicapp/(.*) /myapp/$1 break;
At least it does with my very simple application.
Now I have to figure out how to do proper link translation (sorry for using ISA Server/TMG terms, don't know if it's the same in nginx).
Thanks to pythagor :-)
edit:
Works only if I keep a trailing slash after the url in the browser (https://www.mywebsite.com/publicapp/).
another edit:
To make sure URLs end with a slash:
rewrite ^([^.]*[^/])$ $1/ permanent;
Taken from: here (first answer)

Correct proxy path in nginx.conf

we have two servers, A and B. Server A is accessed worldwide. He has nginx installed. That's what I have in conf:
location /test {
proxy_pass http://localserver.com;
}
What it should do, is translate the addreess http://globalserver.com/test (that is server A) to internal server address http://localserver.com. However, it does append the location path, that is, itries to look for http://localserver.com/test, which is not available at all. How can I make the proxy pass to the correct address, throwing out the last part in the location?
That should work. Nginx should strip the '/test' path on the upstream local server. So what I can say is that is not the cause. To make it a bit better, try this:
location /test/ {
proxy_pass http://localserver.com/;
}
The 2 slashes I added at the first 2 lines will avoid mistakenly match '/testABC' and send the wrong request to the upstream local server, for example.
Do you have a
proxy_redirect
line in the same location block? If your upstream local server has redirects, then a mistake on that line will cause an issue like you described.
[UPDATE] Found the root cause why the original config didn't work and mine works: nginx does NOT replace URI path part if the proxy_pass directive does not have a URI path itself. So my fix of adding a slash (slash is treated as a URI path) at the end triggers the URI path replacement.
Reference: http://wiki.nginx.org/HttpProxyModule#proxy_pass
If it is necessary to transmit URI in the unprocessed form then directive proxy_pass should be used without URI part
location /some/path/ {
proxy_pass http://127.0.0.1;
}
try to add as specified here http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass:
proxy_pass http://localserver.com/;
try rewrite
location /test {
rewrite ^ $scheme://$host/;
proxy_pass http://localserver.com;
}
some helpful links...
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite
http://wiki.nginx.org/Pitfalls

Resources