61

I am trying to proxy a request to different targets depending on an environment variable. My approach was to put the target url into the custom variable $target and give this to proxy_pass.

But using a variable with proxy_pass doesn't seem to work. This simple config leads to a "502 Bad Gateway" response from nginx.

server {
  listen   8080;
  server_name  myhost.example.com;
  access_log  /var/log/nginx/myhost.access.log;
  location /proxy {
    set $target http://proxytarget.example.com;
    proxy_pass $target;
  }
}

The same config without the variable works:

server {
  listen   8080;
  server_name  myhost.example.com;
  access_log  /var/log/nginx/myhost.access.log;
  location /proxy {
    proxy_pass http://proxytarget.example.com;
  }
}

Is it really not possible to use proxy_pass this way or am I just doing something wrong?

Sebastian Heuer
  • 1,022
  • 1
  • 11
  • 17

4 Answers4

82

I've recently stumbled upon this need myself and have found that in order to use variables in a proxy_pass destination you need to set a resolver as your error.log would most probably contain something like no resolver defined to resolve ...

The solution in my case was to setup the following using a local DNS for DNS resolution:

location ~ /proxy/(.*) {
    resolver 127.0.0.1 [::1];
    proxy_pass http://$1;
}

In your case this should work:

location /proxy {
    resolver 127.0.0.1 [::1];
    set $target http://proxytarget.example.com;
    proxy_pass $target;
}

For resolver 127.0.0.1 to work, you need to install bind9 locally. For Debian/Ubuntu:

sudo apt-get install bind9

More information on nginx and dynamic proxy_passing here: http://www.nginx-discovery.com/2011/05/day-51-proxypass-and-resolver.html

Edit: Replaced the previous public DNS with a local one for security issues.

Peter Wippermann
  • 4,125
  • 5
  • 35
  • 48
soulseekah
  • 8,770
  • 3
  • 53
  • 58
  • 3
    FYI, Nginx does not use /etc/hosts, with or without resolver: http://serverfault.com/questions/357719/nginx-domain-resolver – richardkmiller Oct 17 '13 at 18:48
  • 5
    Doesn't work for me. I set an variable, but it seems that nginx can't resolve it if used in proxy_pass directive. In proxy_redirect the same variable works as expected. – shylynx Feb 10 '14 at 10:43
  • 3
    Do **NOT** use a publicly accessible DNS server such as `8.8.8.8`. [To prevent DNS spoofing, it is recommended configuring DNS servers in a properly secured trusted local network.](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) – Tim Oct 07 '16 at 20:10
  • 4
    Resolver is required for fully-qualifed domain names, NOT variables. If the variable you're passing contains an IP address, it works fine. – Spencer Kormos Dec 16 '16 at 20:10
  • 4
    For anyone using Kubernetes you can use the internal Kube DNS service: `resolver kube-dns.kube-system.svc.cluster.local valid=5s;` http://stackoverflow.com/questions/43326913/nginx-proxy-pass-directive-string-interpolation/43341304#43341304 – Mikhail Janowski Apr 11 '17 at 09:09
  • Use **127.0.0.1 [::1]** instead of **8.8.8.8** to prevent DNS spoofing. You also need to install **bind9** "sudo apt-get install bind9" for Debian/Ubuntu. – Amri Mar 30 '18 at 11:58
  • This pattern ~ /proxy/(.*) doesn't seem to match query string – Andriy F. Nov 05 '18 at 14:34
  • You made my day ! Thanks – Holgrabus Nov 26 '20 at 00:40
  • I understand Nginx has its own mechanism to resolve a domain, but it doesn't explain why `proxy_pass http://proxytarget.example.com` works. – Weihang Jian Jul 28 '21 at 04:58
30

Even though the answer of @soulseekah is complete and correct I want to post an answer for the folks using Nginx inside a cluster of containers, being those inside Kubernetes or Docker Compose.

Basically you have to configure a resolver for Nginx with the address of your actual DNS resolver. For Docker it is always at 127.0.0.11, for Kubernetes refer to this answer

Inside my docker network I was able to successfully configure a dynamic proxy_pass by doing so:

resolver 127.0.0.11 [::1];
set $bcknd http://$http_XBackend$uri$is_args$args;
proxy_pass        $bcknd;

Note that it was fundamental to add the $uri$is_args$args since otherwise the proxy pass didn't take in consideration the path and the query string.

PS: in my example, I am reading an header using the $http_XBackend variable. The header is passed by the client as XBackend: host, here the host should be the hostname where you want to forward your calls. I tried using headers with dashes in them with no luck, I had to use an header without dashes.


Edit 16 Jul 2020: Docker doesn't report any more on their webpage the address for the default DNS server. It is still 127.0.0.11:53. If you want to see this value inside your container you need to run cat /etc/resolv.conf.

Naramsim
  • 8,059
  • 6
  • 35
  • 43
  • 4
    I'll add a commentary here: Seems like the '127.0.0.11' DNS server is only available for user defined networks, not for default networks, so I'd be wary on using it unless checked working before :P :D – Andor Jan 18 '19 at 12:40
1

Stumbled on the same exact issue

proxy_pass wasn't resolving my variables, until we found out our DNS server had a problem

can be checked with this cmd by the way

nslookup your-domain your-dns-ip
-2
 location / {
   if ($args ~ "^url=(.+)") { #gets the "url" get parameter
       set $key1 $1;
     proxy_pass $key1;#use the parameter as proxy address
 }
}