Need SSL state to be visible behind a double nginx proxy
Nick Pearson
nick.pearson at gmail.com
Wed Oct 29 23:14:25 MSK 2008
Hi,
I have what I think is an obscure problem to which I cannot find a
solution in the nginx docs. Here's a summary of the problem:
I have a content management system that I wrote (using Rails), and it
hosts several sites. Each site has SSL, and due to a policy limit by
my hosting provider, each virtual server ("slice") can only have five
external IP addresses. This creates a problem in that the CMS that
was built to host lots of sites is limited to hosting only five. My
solution was to set up smaller slices in front of the main server, and
the small slices would run nothing but nginx. As such, the
"front-end" nginx instances would proxy requests to the "back-end"
nginx, and the "back-end" nginx would proxy requests through to the
Rails app server instances.
I saw a recent post on SSL pass-through, but this is not what I'm
looking for. As the slices are on the same network at my hosting
provider, I don't care that the slice-to-slice communications are
encrypted. This is not an issue.
What is an issue is that the application (and thus the Rails app
server instances) need to be able to determine whether a request came
in using SSL or not. Because requests are proxied through two
instances of nginx running on separate slices, the back-end nginx
cannot see whether a request came in over http or https -- because
either way, the request arrived at the back-end nginx over http (not
https). Here is a simple depiction of two requests -- one over http
and one over https:
client ---http---> nginx ---http---> nginx ---> rails
client ---https--> nginx ---http---> nginx ---> rails
As you can see, it is the second (back-end) nginx instance in the
chain that tells the Rails app server instances whether the request
made to nginx was over http or https. And since all requests to the
second nginx instance are made over http, it passes this on to the
Rails app, which thinks all requests are made over http even if the
communication between the client and the first (front-end) nginx is
over https.
Also worth noting is that there are no SSL errors. If the request is
made over http, it works. And if the request is made over https, it
works. The app server just doesn't know whether a request was made
over http or https.
I attempted to set the following on the back-end nginx:
proxy_set_header X_FORWARDED_PROTO $scheme;
but not surprisingly, it doesn't work. ($scheme is either "http" or
"https", taken directly from the request.) I may have a solution to
the problem, but it will require some extra plumbing work in Rails to
make it functional. What I am able to do is to set a custom header
and then read the value of that header in the Rails code. Here is
what I've tried:
proxy_set_header HTTPS on;
Then, in the Rails code, I can look for the HTTP_HTTPS environment
variable. (nginx apparently prepends anything sent to
proxy_set_header with "HTTP_".) While this solution will probably
work, I would prefer to have nginx pass the request scheme to the app
servers without having to change the internals of Rails (by rewriting
the request.ssl? method).
This could be accomplished if nginx would let me conditionally set a
header based on the value of another header. I just can't figure out
how to read a header. Here's what I would like to do -- keep the
"proxy_set_header HTTPS on;" (above) on the front-end nginx and use
the header on the back-end nginx -- like this:
if ($HTTP_HTTPS = on) {
proxy_set_header X_FORWARDED_PROTO https;
}
I realize that this means that anyone could arbitrarily add this
header to a request and trick the Rails server into thinking the
request was secure when it was not, but I would solve this by setting
"ignore_invalid_headers on;" or similar on the front-end nginx.
Can anyone tell me whether it is possible to read custom headers and
to act on them as in my example above? If not, I think this would be
a great addition.
Thanks in advance.
Nick Pearson
More information about the nginx
mailing list