I've got a case where I want to proxy a particular call down to a different backend based on the existence of a query param. The following is sort of what I start with
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
What I'd like to do is check for a query param foo (or even just the existence of that string anywhere). So I thought I could do this
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation"
if ($request_url ~ .*foo.*) {
set $backend_url "someotherlocation"
proxy_pass $backend_url
}
proxy_pass $backend_url;
}
But this doesn't seem to actually proxy to the new location. Am I doing something wrong with my code, or is the whole approach wrong?
I don't know why are you using two proxy_pass directives, this block should do it in a logic you described:
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation";
if ($request_url ~ \?(.*&)?foo(=|&|$)) {
set $backend_url "otherlocation";
}
proxy_pass $backend_url;
}
I slightly modified your regex to match only request URLs where foo is a query argument name and not a query argument value (or its part). However I'd rather use map directive for this purpose (map block should be placed outside the server block):
map $arg_foo $backend_url {
"" somelocation; # if the value is empty
default otherlocation; # otherwise
}
server {
...
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
}
Pay an attention you may need to define a resolver for this configuration to work (some additional info about this can be found here).
Related
In Nginx I need to proxy pass some of the request made to the homepage.
Specificaly, I need to proxy pass them in case of URLs like:
example.com
example.com/
example.com/?anyTypeOfParameterButNotP
However, when the request contains ?p= parameter in the URL, I would like to handle it the regular way (as if there wasn't any proxy_pass at all).
So, the URLs I would like not to proxy pass would be:
example.com/?p=1234
example.com/?p=1234&foo=bar
How can I achieve that?
I've tried some if statements, some variables solutions, but I didn't figure out any way to work it out.
If you are using something that can end with "break;"
location / {
if ($query_string ~ p=) {
## regular way - try_file? and end with "break;"
}
proxy_pass http://somwhere;
}
--- or here is an if-else condition for unusual cases
location / {
set $skip_proxy 0;
if ($query_string ~ p=) {
set $skip_proxy 1;
}
if ($skip_proxy = 1) {
# when query does contain ?p=
}
if ($skip_proxy = 0) {
# when query does NOT contain ?p=
# proxy_pass http://somwhere; ?
}
}
How can I deny access to nginx if the path contains /local or /local-int to all networks except the local one?
For example https://example.com/api/local/settings. I tried this, but when accessed locally, the request goes to /etc/nginx/html/api/local/settings,and not to the desired backend
location = (local|local-int) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}
I have about 20 such sites, and I'm trying to come up with a solution that would not be tied to a specific location
I summarize: if I access a site from allowed ip, then it should show the page to which I am accessing, and if from a deny list, then 403
Config example:
server {
listen ip:80;
listen ip:443 ssl;
server_name test.com;
if_modified_since off;
location /api {
proxy_pass https://api.example.com;
}
location ~ (\/local) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}
}
This will simply work with both of your locations, since both starts with /local
location ~ (\/local) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}
Nginx takes a = location modifier as an exact match (docs are here). If you want to make a location that will catch every URI containing /local substring (obviously including /local-int), you can use a regex one:
location ~ /local {
...
}
The ^~ modifier makes the location block in #user973254 answer (original answer version, already fixed) a prefix one with the greater priority than any regex locations, so it will overtake only the URIs starting with /local (obviously not including /api/local/settings from your example).
However if your web backend requires an additional URI processing (which is a most common case nowadays), you'll need at least to replicate your main location behavior with this new location. Fortunately, there is a way to avoid such a problems, and can be easily applied to an arbitrary number of sites as you ask for in your original question. You can check required conditions to make a decision for blocking the request or not using the (very powerful) map block feature. And since we want to match address against a list of subnets, we will use a chain of map and geo blocks. To use regexes (PRCE/PCRE2 syntax) for a map block match use a ~ string prefix (~* for case-insensitive match), strings containing some special characters (e.g. curly braces) should be single- or double-qouted. Here is a generic example (you'll need only the first line of the following map block to fulfill your question requirements):
map $uri $restricted {
~/local 1; # regex to match any URI containing '/local' substring
~^/private/ 1; # regex to match any URI starting with '/private'
~*\.jpe?g$ 1; # regex to match any URI ending with '.jpg' or '.jpeg' (case-insensitive)
/some/protected/page/ 1; # exact URI match (string isn't starting with '~')
... any number of additional rules here
default 0;
}
geo $deny {
10.150.0.0/16 0;
10.160.0.0/16 0;
10.170.0.0/16 0;
default $restricted;
}
server {
...
if ($deny) { return 403; }
...
}
You can swap the logic to check the URI first (it can be some performance impact since the regex matching will be performed for every request including requests from the non-restricted networks, however if the majority of requests come from public addresses, there will be no significant difference). That way you can have a common non-restricted subnes list and per-site URI lists:
geo $restricted {
10.150.0.0/16 0;
10.160.0.0/16 0;
10.170.0.0/16 0;
default 1;
}
map $uri $deny1 {
~/local $restricted;
default 0;
}
map $uri $deny2 {
~^/admin $restricted;
default 0;
}
server {
server_name site1.com;
if ($deny1) { return 403; }
...
}
server {
server_name site2.com;
if ($deny2) { return 403; }
...
}
Of course, you are not limited to use 403 return code using this solution (which is the case when you are using allow/deny directives). It also has nothing to do with the famous "If is evil" article since this if is used in server context.
I am trying to use variable to set the hostname in a proxy_pass, but once I try that, the path after the location is ignored.
If I try and get localhost:8001/dirA/x/y/z.html. The following returns the file from http://server1:8888/dirB/dirC/x/y/z.html. This is what I expect to happen.
location ^~ /dirA/ {
proxy_pass http://server1:8888/dirB/dirC/;
But if I try the following config which is just using a variable for hostname... and try and get localhost:8001/dirA/x/y/z.html
location ^~ /dirA/ {
set $endpoint server1;
proxy_pass http://$endpoint:8888/dirB/dirC/;
I get http://server1:8888/dirB/dirC/index.html returned instead.
That's just how proxy_pass works. If you use a variable in the value, you need to provide the entire URI. See this document for details;
You could use a regular expression location. For example:
location ~ ^/dirA/(.*)$ {
set $endpoint server1;
proxy_pass http://$endpoint:8888/dirB/dirC/$1;
}
Note that the order of regular expression locations is significant. See this document for details.
Alternatively, a rewrite...break should also work.
location ^~ /dirA/ {
set $endpoint server1;
rewrite ^/dirA/(.*)$ /dirB/dirC/$1 break;
proxy_pass http://$endpoint:8888;
}
I am trying to resolve proxy_pass value dynamically (through web api) in nginx.
I need something like below;
Example taken from: https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
location /proxy-pass-uri {
set $urlToProxy = CallWebAPI("http://localhost:8081/resolver?url=" + $url);
proxy_pass $urlToProxy;
}
So, my question is that, is it possible to make HTTP request or to write method such as CallWebAPI?
I know it might be a bad practice, but the website I am dealing with has thousands of web urls, which are mapped as key-value pairs, and 90% of them does not obey any specific regex rules. So I have content mapped database, and I need to fetch incoming url with content dynamically.
I am trying to use a very light web service to look up URLs from redis, and return proxy url.
Would this be a valid scenario, or is there any other built in solution in nginx like this?
I doubt this can be done with "pure" nginx, but this definitely can be done with openresty or ngx_http_lua_module with the help of ngx.location.capture method. For example:
resolver 8.8.8.8;
location ~/proxy-pass-uri(/.*)$ {
set $url $1;
set $proxy "";
access_by_lua_block {
res = ngx.location.capture("http://localhost:8081/resolver?url=" .. ngx.var.url)
ngx.var.proxy = res.body
}
proxy_pass $proxy$url;
}
There is also an ngx_http_js_module (documentation, GitHub) which have an ability to do subrequests (example), but I never used it and cannot tell if it can be used this way.
Important update
After almost a three years since this answer was written, it comes that I needed the similar functionality myself, and it turns out that the above answer is completely broken and unworkable. You can't do a subrequest via ngx.location.capture to anything else but to some other nginx location. So the correct (checked and confirmed to be workable) example for the above question is
resolver 8.8.8.8;
location /resolver {
internal;
proxy_pass http://localhost:8081;
}
location ~ ^/proxy-pass-uri(/.*)$ {
set $url $1;
set $proxy "";
access_by_lua_block {
res = ngx.location.capture("/resolver?url=" .. ngx.var.url)
if res.status == ngx.HTTP_OK then
ngx.var.proxy = res.body
else
ngx.exit(res.status)
end
}
proxy_pass $proxy$url$is_args$args;
}
The above example assumes that the proxy resolution service is really expecting request in a /resolver?url=<uri> form. The location /resolver { ... } while being internal behaves like any other prefix location, so if the /resolver prefix for that location cannot be used for some reason, the same can be written as
resolver 8.8.8.8;
location /get_proxy {
internal;
proxy_pass http://localhost:8081/resolver;
}
location ~ ^/proxy-pass-uri(/.*)$ {
set $url $1;
set $proxy "";
access_by_lua_block {
res = ngx.location.capture("/get_proxy?url=" .. ngx.var.url)
if res.status == ngx.HTTP_OK then
ngx.var.proxy = res.body
else
ngx.exit(res.status)
end
}
proxy_pass $proxy$url$is_args$args;
}
I need to set proxy_pass for the below url pattern.
location ~ ^/hosts/bu/(.*)/app/(.*)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$2;
}
When I try with the URL it does not pass the second parameter correctly. Please let me know where I am going wrong.
Orig docs say:
If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI:
location /some/path/ {
proxy_pass http://127.0.0.1;
}
So there is enough:
proxy_pass http://appserver.cnma.com:3000$request_uri;
you can use named capture in nginx while using regular expressions
location ~ ^/hosts/bu/(.*)/app/(.*)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$2;
}
You can modify block to below like this
location ~ ^/hosts/bu/(?<loc1>.+)/app/(?<loc2>.+)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$loc2;
}