Custom routing in load balancer - nginx

Is it possible to define a custom routing in NGINX or other Load Balancer?
I.e. I have a cookie or a header and based on its value I decide which backend server to choose?
I need some very simple logic - values a1,a2,a3 - to server A, values b1,b2 to server B, all other to server C

In nginx you can do it simply by using if:
location / {
if ($http_cookie ~* "yourcookiename=a") {
proxy_pass http://upstream_a;
break;
}
if ($http_cookie ~* "yourcookiename=b") {
proxy_pass http://upstream_b;
break;
}
proxy_pass http://upstream_c;
}
This is simple regexp , so this way if "yourcookiename" has value a1,a2 etc. it will go to uprstream_a and so on.
Hope it helps...

If you need some sticky session, there are open source third party modules that can do that with nginx, while the native implementation is part of the commercial subscription. Also, tengine, an open source chinese fork of nginx developed by Alibaba can do that natively.
If you want to do it the custom way, use a map to avoid processing a chain of if blocks for all requests. This is also better for readability. For instance, using a cookie :
map $cookie_mycookie $node {
"~^a[1-3]$" "A";
"~^b[1-2]$" "B";
default "C";
}
server {
location / {
proxy_pass http://$node;
}
}

Related

NGINX Ingress Redirection Based On Domain Name

I have two domain names, each for different applications hosted in a single kubernetes cluster.
Is there a way to configure ingress to redirect to the different apps based on the hostname in the request it receives?
For example:
www.app1.com and www.app2.com point to the same IP address. However, I want www.app1.com to redirect to /appABC while www.app2.com redirect to /appXYZ.
I have attempted to capture the host name and use this to determine the redirect but it doesn't work.
Is what I'm trying to do possible with NGINX?
Yes,it is Possible. You must need to create two configuration files and point them to their respective paths. Please follow this link for more info and refer to this SO also to get further idea on how to use.
After some experimentation, using the NGINX Playground, I was able to come up with this solution.
...
nginx.ingress.kubernetes.io/server-snippet: |
set $is_app1_base 1;
set $is_app2_base 1;
if ($host !~ "^.*app1\.com$" ) {
set $is_app1_base 0;
}
if ($request_uri != "/") {
set $is_app1_base 0;
set $is_app2_base 0;
}
if ($is_app1_base = 1) {
return 301 $scheme://$host/appABC;
}
if ($host !~ "^.*app2\.com$" ) {
set $is_app2_base 0;
}
if ($is_app2_base = 1) {
return 301 $scheme://$host/appXYZ;
}
In case you're wondering why a number of if statements had to be used this way, NGINX is not that great with if statements and logical operations.
Another caveat worth stating here is that all ingresses associated with this NGINX controller will be affected by this server-snippet; Because nginx.ingress.kubernetes.io/server-snippet is a global annotation.

Calling external api in Nginx location section

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;
}

Nginx permanent redirect to different url for certain subdirectory

I have 2 Nginx servers serving static files from 2 different subdomains of an unknown parent domain, let's say <env>.foo.<domain>.com and <env>.bar.<domain>.com.
I want to configure the nginx server for <env>.foo.<domain>.com so that if the url has the subdirectory cat or dog I want to redirect to <env>.bar.<domain>.com/<subdirectory>/<rest of url>.
E.g.
http://dev.foo.mydomain1.com/cat/22 -> http://dev.bar.mydomain1.com/cat/22
http://dev.foo.mydomain1.com/dog/22 -> http://dev.bar.mydomain2.com/dog/22
http://dev.foo.mydomain2.com/dog/22 -> http://dev.bar.mydomain2.com/dog/22
http://dev.foo.mydomain1.com/bird/22 -> [no redirect]
The <env> and <domain> portions of the domain are dynamic depending to the environment to which the servers are deployed, but are common between the 2 nginx boxes.
I imagine it being something like:
server {
location ??? /(cat|dog) {
return 301 $scheme://???/$1$is_args$query_string;
}
}
But my nginx skills are not quite there...
rewrite ^/oldlocation$ http://www.newdomain.com/newlocation permanent;
Refer digital oceans's article on "How To Create Temporary and Permanent Redirects with Nginx" for further details.
What you can do is below
http {
map $http_host $host_to_send_to {
default $http_host;
dev.foo.mydomain1.com dev.bar.mydomain1.com;
dev.foo.mydomain2.com dev.bar.mydomain2.com;
}
server {
server_name dev.foo.mydomain1.com;
location /(cat|dog) {
return 301 $scheme://$host_to_send_to$request_uri$is_args$query_string;
}
}
}
You will need to add it to each server block which needs to handle the redirect.
Edit-1: Dynamic host name handling
You can handling host names dynamically also user re patterns and groups
map $hostsname $host_to_send_to {
default $http_host;
~(.*).foo.mydomain1.com $1.bar.mydomain1.com;
~(.*).foo.mydomain2.com $1.bar.mydomain2.com;
}

How should I configure Nginx to proxy to a URL passed by parameter?

I'm trying to get access to media files (images, videos) sitting behind an OAuth2 authentication.
In order to access the resource I need to add a custom Authorization Bearer token to the request, so I can't use a simple rewrite (well, as far as I know at least).
It cannot be done via plain HTML (say img or video tag) so I'm considering to have Nginx proxying the queries to the final server.
Each of the media resources would be loaded via a /proxy path, with a token parameter (for authentication) and url for the actual resource to load.
Sample URL:
http://myserver.com/proxy/?token=12345&url=http://protectedserver.com/custompath/asset
This is what I came up with but I am not quite sure how to configure the proxy_pass directive since I need it to proxy to the $url variable specifically. I do not need to proxy the path (which would be empty anyway).
location /proxy/ {
if ($arg_token ~ "^$") { return 404; }
if ($arg_url ~ "^$") { return 404; }
set $url $arg_url;
proxy_set_header Authorization "Bearer $arg_token";
set $args "";
#proxy_pass $url;
}
Note: this will be run in a closed environment and only specific machines (kiosks with limited interaction) will be able to access the page so I'm not concerned about a potential leak of the auth token.
I noticed a similar question on ServerFault, but no one had an answer to that:
https://serverfault.com/questions/671991/nginx-proxy-pass-url-from-get-argument
I'm looking for a config setting to make it work or a viable alternative solution.
Here is a correct configuration for my problem:
location /proxy/ {
if ($arg_token ~ "^$") { return 404; }
if ($arg_url ~ "^$") { return 404; }
set $url $arg_url;
set $token $arg_token;
set $args "";
# IMPORTANT, this is required when using dynamic proxy pass
# You can alternatively use any DNS resolver under your control
resolver 8.8.8.8;
proxy_pass $url;
proxy_set_header Authorization "Bearer $token";
proxy_redirect off;
}

How to use nginx as reverse proxy to direct localhost:9292 to a sub domain foo.localhost/?

I've learned how to pass localhost:9292 to localhost/foo with the following directive:
location /foo {
proxy_pass http://localhost:9292;
}
but I want do something like
foo.localhost -> localhost:9292
Is there a way I can do that?
If foo.localhost is your sub domain name and you want to proxy pass sub-domain to main-domain, you can use proxy_pass and you can learn a little more about server directive if needed. An example:
server {
listem 8080;
host sub.main.com;
...
location / {
proxy_pass http://main.com;
break;
}
}
server {
listen 8081;
host main.com;
...
location / {
//do something
}
}
This is proxy pass, means when access sub.main.com, actually it finally dealt by main.com, but the client side still shows sub.main.com. If you want client side shows main.com, here should use redirect but not proxy_pass.

Resources