How to fix Sinatra redirecting https to http under nginx
Asked Answered
O

2

14

I have a Sinatra app running in nginx (using thin as a back-proxy) and I'm using redirect '/<path>' statements in Sinatra. However, when I access the site under https, those redirects send me to http://localhost/<path> rather than to https://localhost/<path> as they should.

Currently, nginx passes control to thin with this command proxy_pass http://thin_cluster, where thin_cluster is

upstream thin_cluster { server unix:/tmp/thin.cct.0.sock; }

How can I fix this?

On answered 13/6, 2011 at 12:48 Comment(2)
Does adding proxy_set_header X-Forwarded-Proto $scheme; after your proxy_pass line help?Wheelbarrow
You should write that as an answer... it fixed my problem. Thanks!On
W
22

In order for Sinatra to correctly assemble the url used for redirects, it needs to be able to determine whether the request is using ssl, so that the redirect can be made using http or https as appropriate.

Obviously the actual call to thin isn't using ssl, as this is being handled by the front end web server, and the proxied request is in the clear. We therefore need a way to tell Sinatra that it should treat the request as secure, even though it isn't actually using ssl.

Ultimately the code that determines whether the request should be treated as secure is in the Rack::Request#ssl? and Rack::Request#scheme methods. The scheme methods examines the env hash to see if one of a number of entries are present. One of these is HTTP_X_FORWARDED_PROTO which corresponds to the X-Forwarded-Proto HTTP header. If this is set, then the value is used as the protocol scheme (http or https).

So if we add this HTTP header to the request when it is proxied from nginx to the back end, Sinatra will be able to correctly determine when to redirect to https. In nginx we can add headers to proxied requests with proxy_set_header, and the scheme is available in the $scheme variable.

So adding the line

proxy_set_header X-Forwarded-Proto $scheme;

to the nginx configuration after the proxy_pass line should make it work.

Wheelbarrow answered 15/6, 2011 at 21:56 Comment(3)
For some reason this screwed up the Host field in my headers, sending all my redirect_to's to invalid domains. Adding "proxy_set_header Host $host" fixed it. (My app is running under a sub-uri, and I wonder if that is an issue.)Meghanmeghann
@Meghanmeghann you could also use X-Forwarded-Host, which rack checks before checking the Host header. I doesn't look like nginx automatically adds these headers, but Apache does in mod_proxy.Wheelbarrow
Yes, I suppose that would work too. Found an interesting explanation at wiki.nginx.org/HttpProxyModule#proxy_set_header. I was setting proxy_set_header Host in the http block, for use with all my apps. Based on your post, I added proxy_set_header X-Forwarded-Proto in location @myapp. According to the docs, "proxy_set_header directives issued at higher levels are only inherited when no proxy_set_header directives have been issued at a given level." So my "local" proxy_set_header call in @myapp was clearing my "global" proxy_set_header calls, even though they were for different fields.Meghanmeghann
Y
1

You can force all links to go to https in the nginx layer. in nginx.conf:

server{
   listen 80;
   server_name example.com;
   rewrite    ^(.*) https://$server_name$1 redirect;
}    

This is good to have too to assure that your requests are always https

Ypsilanti answered 14/6, 2011 at 21:29 Comment(1)
This is helpful... but doesn't answer the question I asked. I already have this functionality working if the server is running in HTTPS-only mode; my problem happens when I want to accept both HTTPS and HTTP.On

© 2022 - 2024 — McMap. All rights reserved.