I want to have my API controller use SSL, so I added another listen directive to my nginx.conf
upstream unicorn {
server unix:/tmp/unicorn.foo.sock fail_timeout=0;
}
server {
listen 80 default deferred;
listen 443 ssl default;
ssl_certificate /etc/ssl/certs/foo.crt;
ssl_certificate_key /etc/ssl/private/foo.key;
server_name foo;
root /var/apps/foo/current/public;
try_files $uri/system/maintenance.html $uri/index.html $uri @unicorn;
location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://unicorn;
}
error_page 502 503 /maintenance.html;
error_page 500 504 /500.html;
keepalive_timeout 5;
}
which passes the nginx conftest without any problems. I also added a force_ssl
directive to my ApiController
class ApiController < ApplicationController
force_ssl if Rails.env.production?
def auth
user = User.authenticate(params[:username], params[:password])
respond_to do |format|
format.json do
if user
user.generate_api_key! unless user.api_key.present?
render json: { key: user.api_key }
else
render json: { error: 401 }, status: 401
end
end
end
end
def check
user = User.find_by_api_key(params[:api_key])
respond_to do |format|
format.json do
if user
render json: { status: 'ok' }
else
render json: { status: 'failure' }, status: 401
end
end
end
end
end
which worked just fine when I wasn't using SSL, but now when I try to curl --LI http://foo/api/auth.json
, I get properly redirected to https
, but then I keep on getting redirected to http://foo/api/auth
ending in an infinite redirect loop.
My routes simply have
get "api/auth"
get "api/check"
I'm using Rails 3.2.1 on Ruby 1.9.2 with nginx 0.7.65
Try setting this directive in your nginx
location @unicorn
block:proxy_set_header X-Forwarded-Proto https;
I had this same issue and investigating the Rack middleware handler (not
force_ssl
but similar) I could see that it was expecting that header to be set to determine if the request was already processed as being SSL by nginx.You're not forwarding any information about whether this request was an HTTPS-terminated request or not. Normally, in a server, the "ssl on;" directive will set these headers, but you're using a combined block.
Rack (and force_ssl) determines SSL by:
See the force_ssl source for the full story.
Since you're using a combined block, you want to use the third form. Try:
in your server or location block per the nginx documentation.
This will set the header to "http" when you come in on a port 80 request, and set it to "https" when you come in on a 443 request.