89

I run several docker containers with hostnames:

web1.local web2.local web3.local

Routing to these done based on hostname by nginx. I have a proxy in front of this setup (on different machine connected to internet) where I define upstream as:

    upstream main {
      server web1.local:80;
      server web2.local:80;
      server web3.local:80;
    }

And actual virtual host description:

    server {
      listen 80;
      server_name example.com;
      location / {
        proxy_pass http://main;
      }
    }

Now, because containers receive hostname "main" instead of "web1.local", they do not respond properly to the request.

Question: how I can tell nginx to pass name of the upstream server instead of name of upstream group of servers in Host: header when proxying request?

  • 2
    I don't think you can. Why don't you set your backend servers to respond to main or example.com? It isn't as if the backend doesn't know who it is. The reverse is readily possible: proxy_set_header Host $host; will replace any Host variable coming back from the upstream with the hostname from the original request. – Andrew Domaszek May 23 '14 at 22:28
  • The proper thing to do is to fix the application. – Michael Hampton May 25 '14 at 12:59
107

Actually you can do that via proxy_set_header.

For more details look here: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header or see an example use-case here: https://stackoverflow.com/questions/12847771/configure-nginx-with-proxy-pass

I have included the dynamic approach into your above posted configuration:

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $host;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}

Here is an example with a static host name:

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            www.example.com;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}
  • 7
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; seems better – sivann May 24 '14 at 9:00
  • 1
    @pavel: got it. Actually I did also some research and some tests. It seems that there is no straight approach to fulfill your requirement. So even a "bastardized" solution is a solution. I don't like to ask why you'd like to do this. I'm pretty sure you have your reasons. :-) – Jens Bradler May 25 '14 at 11:37
  • @JensBradler You seem more expert than me so, could you tell me what you think of my solution? I want to do the same because I run two copies of my website from two accounts on my ISP: site1.myisp.com and site2.myisp.com and they only respond to their respective name. I, now, own my domain name and I'd like to use my ISP website to load balance my servers. Isn't that a good reason? Thank you very much ;) – ncenerar Aug 20 '14 at 15:45
  • 1
    @ncenerar You can do that but this will bring you to a single point of failure: the load balancer. If this is for load balancing (not redundancy) you can also use DNS based load balancing in combination with DNS failover. – Jens Bradler Sep 24 '14 at 18:28
  • 1
    This answer reflects advice of the official blog. – Bernard Rosset Apr 4 '16 at 16:05
27

I had the same problem and I finally solved it by using two levels of proxy. Here is how you could do for your situation (I think):

server {
  listen      8001 default_server;
  server_name web1.example.com;
  location / {
    proxy_pass       http://web1.local:80;
    proxy_set_header Host web1.local:80;
  }
}

server {
  listen      8002 default_server;
  server_name web2.example.com;
  location / {
    proxy_pass       http://web2.local:80;
    proxy_set_header Host web2.local:80;
  }
}

server {
  listen      8003 default_server;
  server_name web3.example.com;
  location / {
    proxy_pass       http://web3.local:80;
    proxy_set_header Host web3.local:80;
  }
}

upstream main {
  server 127.0.0.1:8001;
  server 127.0.0.1:8002;
  server 127.0.0.1:8003;
}

server {
  listen      80;
  server_name example.com;
  location / {
    proxy_pass http://main;
  }
}

As you can see, the trick is to create a local server responding to a particular port that will proxy the server by rewriting the right Host for each servers. Then, you can use this local servers in your upstream and finally use that upstream in the real proxy.

  • I originally used Lua approach, but now switched completely to HAProxy which allows to do just what I wanted with standard configuration. – pavel_karoukin Aug 20 '14 at 18:58
3

So from reading all documentation for nginx (I could not really parse code for upstream module =( ) I came up with this bastardized solution. Unfortunately this solution does not keep track of failed hosts, but simply select random one and redirect request to it. So I have to setup some kind of monitoring to make sure all backends are running.

server {
        listen 80;
        server_name example.com;
        resolver 127.0.0.1;

        location / {
                set $upstream "";
                rewrite_by_lua '
                        local upstreams = {
                                "http://web1.dokku.localdomain",
                                "http://web2.dokku.localdomain",
                                "http://web3.dokku.localdomain",
                                "http://web4.dokku.localdomain"
                        }
                        ngx.var.upstream = upstreams[ math.random( #upstreams ) ] 
                ';
                proxy_pass $upstream;
        }
}
2

We pass in the upstream addr as a separate header like this

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    add_header       X-Upstream      $upstream_addr;
  }
}

What if you tried?

server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            $upstream_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    add_header       X-Host          $host;
  }
}
2

While the goal seems logical, nginx isn't going to change the Host: header to match the upstream. Instead, it treats upstream domain names like a CNAME in DNS - as a way to get to an IP address.

The request headers (and body) are fixed before the upstream is selected. The upstream may change mid-request if it's a particular upstream is found to be non-responsive, but the request doesn't change.

0

Hmm. I have a similar setup, in which I've simply done

location / {
    ... 
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_pass ...;
}

The use of $http_host (the HTTP Host header from the incoming request) here rather than $host (the server hostname configuration) causes the same Host header passed by the client to be passed up to the upstream, in my testing.

See also https://stackoverflow.com/questions/14352690/change-host-header-in-nginx-reverse-proxy.

0

As other people already posted using script variable (like $upstream), you can set it however way you like, and that will fix the issue, without additional header hacking.

Proxy Pass handler threat script variables in a different way, if a value is not conditional (does not have $ in the name) is backed to the upstream on configuration phase and use later.

A simple way to omit this issue, and have the most advantages of (free version) upstream would be using something like Split_Clients:

split_clients $request_uri $my_upstream {
              33%          server1.domainX.com;
              33%          server2.domainX.com;
# Always use DOT at end entry if you wonder why, read the SC code.
              *            server3.domainX.com;  
}
location / {
    ... 
    proxy_pass http://$my_upstream;
}

The above example looks almost the same as upstream. There exists other modules do the mapping, i.e. chash_map_module, but as they are out of the tree you will need to build them by your own, which is not possible for some use-cases/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.