I'm having a nightmarish problem getting IE 9 to redirect from http to https. I've looked at a discussion from about a year ago (http://groups.drupal.org/node/206813), but in that case, nginx is in complete control.
In my case, I'm in AWS, and https is actually getting handled by an elastic load balancer (ELB), which is proxying over to nginx over port 80, and setting http_x_forwarded_proto:
#x_forwarded_proto stuff for elb/https issues - see http://daniel.hahler.de/handle-x-forwarded-proto-in-backend-nginx
set $my_https "off";
if ($http_x_forwarded_proto = "https") {
set $my_https "on";
}
# http to https rewrite
if ($http_x_forwarded_proto = "http") {
rewrite ^(.*) https:/$host$1 redirect;
}
This works fine on a well engineered, stable browser. Unfortunately, IE 9 is not one of those. If security is set to typical levels of paranoia by the end user, IE gives a cryptic "Internet Explorer cannot display the web page" error with a helpfully unhelpful "Diagnose Connection Problem" that craps out and says, in so many words "you're on your own brother".
I suspect that some of you have seen this. Is there a better way to do this redirect so that IE 9 will, well, behave.
Using Fiddler 2, I see that our server (going back from nginx via the ELB) returns this:
HTTP/1.1 302 Moved Temporarily Content-Type: text/html Date: Thu, 16 May 2013 01:11:08 GMT Location: https:/preview.our-site-name.org/ Server: nginx/1.2.6 Content-Length: 160 Connection: keep-alive
IE responds this with its "Cannot display web site" error, w/o any further explanation.
Is there an nginx specific set up that will fix this? Or is there something we need to do on the ELB?
Also, anybody that can tell me how to get IE9 to give useful (or any) diagnostics will gain much karma, and the esteem of many of his or her peers.

Comments
schema
I see https:/preview.our-site-name.org/ shouldn't this be https://preview.our-site-name.org/ as it it needs 2 "/"
Also, and I'm sure @perusio
Also, and I'm sure @perusio will weigh in with some more experience, what if you did your redirect like this: http://serverfault.com/questions/67316/in-nginx-how-can-i-rewrite-all-ht... instead of using all of the if statements?
Yep
you have a missing
/could be that. Also that blog post makes reference to a very old version. There's no need to explicitly set the HTTPS FCGI param.You should use a server block like this:
server {listen 80;
return 301 https://$host$request_uri;
}
Thanks for jogging my memory.
Thanks for jogging my memory. I like that one!
Where does this server block sit in the config
perusio --
I'm using a CentOS 6 deployment which uses a Debian style "sites-available" type of directory.
Right now, the file for this particular server has a single server block. I'm a bit unclear where the redirecting server block you recommend above sites in the file. Should it be first, before the server block with the rest of the settings you detail below?
Instead of a redirect, how
Instead of a redirect, how about something in your drupal settings.php like:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {$base_url = 'https://preview.our-site-name.org';
}
At the end of this issue are
At the end of this issue are some other ways of doing that:
http://drupal.org/node/313145
This one looks nice:
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {$_SERVER['HTTPS'] = 'on';
}
Another idea: fastcgi_param
edited...that should probably be
map $http_x_forwarded_proto $php_https {default '';
https on;
}
...
fastcgi_param HTTPS $php_https;
Much thanks
I'm going to try out these suggestions tomorrow, and see if they resolve the IE 9 related problem.
Even if not, these should be a cleaner and easier maintained version of the code I'm using now.
So continuing
Nginx introduced in version 1.1.11 a flag for variables
if_not_empty. So that the FCGI config has:fastcgi_param HTTP $https if_not_empty;That should be your first shot. If that doesn't work this will fix it:
map $scheme$http_x_forwarded_proto $https {httpshttp on;
httpshttps on;
httpshttp on;
httphttp on;
}
And you use the same line:
fastcgi_param HTTP $https if_not_empty;For the redirect from HTTP to HTTPS use the server block I've written above.
Where does the server block go in the configuration
Perusio --
If I'm in a mulit-site set up, where does this server block go?
This looks to me like an non-conditional redirect. Since I am getting proxied over at port 80 from the ELB for both http and https URLs, would I not want to do that redirect ONLY in the http case? It looks like what you are suggesting would cause a redirect loop if I used it naively (which sadly, is the only way I'm using this stuff right now).
I know that multiple server blocks are greatly preferred to using "if", but I'm not clear how to get what looks like conditional behavior here without it.
Yeah. Look at this issue:
Yeah. Look at this issue: https://forums.aws.amazon.com/message.jspa?messageID=405375
You need to configure ELB so that it sends http -> http and https -> https.
The redirect is inefficient,
The redirect is inefficient, unnecessary and I would guess that the switch from 302 to 301 still won't satisfy ie9.
The front end proxy (ELB in
The front end proxy (ELB in this case) is tasked with handling ssl negotiation and encryption with the client. Communication between the proxy and the back end server should not be encrypted (unless it's over a public network, in which case ELB should not accessing the backend nginx server on port 80).
Take the static assets for example: Someone requests a css file over port 80 and the proxy requests the file from nginx over port 80 and all is well. Now If someone requests a css file over ssl, port 443, then the proxy requests the file from nginx over port 80, unencrypted, nginx returns it unencrypted, and then the proxy encrypts the file and sends it back over ssl. Using the redirect discussed above will cause nginx to rewrite the request, process it again, encrypt it, and send it back to the proxy over ssl. All for no reason and at the cost of some processing power. And it will waste that processing power even on client requests that were not over ssl.
The problem is with the dynamic requests to Drupal. Without any special configuration, an incoming request over ssl will reach the proxy which will make a request to nginx on port 80 and nginx will tell Drupal that the request is not ssl. Drupal will return a page and nginx will send it back to the proxy and the proxy will encrypt it and return it to the client over ssl just fine...however, all the link and script tags and some other stuff embedded in the Drupal page will point to http:// and not https://, since Drupal didn't know any better. At that point, a browser will recognize that the page itself is over ssl but some of the links contained in it are not in ssl. Since the browser doesn't know which are important it defaults to saying that the "mixed" mode of ssl and non-ssl is insecure. Some browsers might put a red line thru the lock symbol and some with stricter security might lock you out entirely.
The solution is to tell Drupal that the page that it is going to return should be written for ssl. Depending on the version of Drupal you're using the bootstrap.inc is going to contain something like:
// Create base URL$base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
and then use $base_root to create $base_url.
So you can use the special HTTP_X_FORWARDED_PROTO header to inform Drupal of the ssl status using php at the Drupal stage in your settings.php with something like
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {$_SERVER['HTTPS'] = 'on';
}
(assuming that ELB sends that header only for https, otherwise you'd have to do a more complex test). Then Drupal will know to send back a page with https links.
Or you're probably better off doing it at the nginx stage with:
map $http_x_forwarded_proto $php_https {
default '';
https on;
}
fastcgi_param HTTPS $php_https if_not_empty;
(Good call on the if_not_empty but you can't use $https as the variable since it's a built-in variable now). The map directive should be at the http level and the fastcgi_param is at the location level, though you can probably just put the line in your fastcgi_params file if Centos is anything like Debian.
The redirect might work most of the time since nginx is telling Drupal that the request is over ssl on every single request, but it's using resources unnecessarily and then on regular requests NOT over ssl, the embedded links will still be https! So every single static file will be requested over ssl, whether the main page is over ssl or not.
i need to redirect some subset of paths to https
Brian,
I'm looking at your suggestions, and trying them out right now. If I go to https://my.site.org, it displays fine. And if I go to http://my.site.org, it displays correctly over HTTP, but does not redirect to https://my.site.org, which is the desired behavior.
I've added
<?phpif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
$_SERVER['HTTPS'] = 'on';
}
?>
to settings.php, added
map $http_x_forwarded_proto $php_https { default ''; https on; }to my nginx.conf file, which is the top-level config file, containing the http {} block, and which calls my file from sites-enabled/ that contains the server{} block below the point I added your map directive.
In mysite.conf, which is called via that include, I have a location block like this; I've added the xx at the end of it, as so:
location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors on; fastcgi_pass unix:/tmp/phpfpm.sock; #fastcgi_param HTTPS $my_https; fastcgi_param HTTPS $php_https if_not_empty; }What am I missing here to make http://my.site.org redirect to https://my.site.org behind an ELB (which is configured as you describe)?
I've started reading Dimitri Aivaliotis' book to help me better understand how configuring nginx works; I'm an apache guy, so I'm still doing this from a low level of comprehension :-)
It ain't necessarily so
In fact you're right. The
httpsvariable has a special behaviour. You cannot override it. But it's not the common case. You can overwrite most variables, e.g.,$scheme. In that respect Nginx handles you the keys and it's your responsability to behave properly.It's the only variable in
ngx_http_variables.cthat is set to the empty string if the scheme is not https.static ngx_int_t
ngx_http_variable_https(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
v->len = sizeof("on") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "on";
return NGX_OK;
}
#endif
*v = ngx_http_variable_null_value; // here it is
return NGX_OK;
}
I suppose that is necessary to make it work with the
if_not_emptyflag.Well then perusio is right on
Well then perusio is right on at http://groups.drupal.org/node/298833#comment-925198
Ideally the front end proxy should be able to do this, but a quick Google search tells me that ELB cannot handle the redirect and you have to do it at the back end.
You need two server sections, one short one like perusio stated:
server {listen 80;
return 301 https://$host$request_uri;
}
and a second one configured for ssl with all of the configuration in it:
server {
listen 443 ssl;
ssl_certificate ...
ssl_certificate_key ...
[All configuration here]
}
ELB is handling SSL
Brian,
Am I understanding you that in general, we cannot have the ELB handle SSL? If so, why is this?
We can certainly move SSL handling from the ELB to nginx (on our instances). I just want to understand why this is.
No, you can and should have
No, you can and should have ELB handle SSL.
What I'm saying is that since ELB can't do the redirect, you'll have to set up ssl on both ELB and nginx.
And yes, that's wasteful, but necessary due to the limitation of ELB.
The guy in this issue is
The guy in this issue is right though: https://forums.aws.amazon.com/message.jspa?messageID=405375
Your front end proxy SHOULD handle this and save the backend the work of encrypting. However, that answer from an AWS tech from 6 months ago says that it is not a feature of ELB.
OK, so I gave it some more
OK, so I gave it some more thought and I spun up an EC2 instance and load balancer and I think there's a better way to do it.
I set the load balancer with two listeners:
http/80 to http/80
https/443 to http/8080
(remember to add 8080 to the security group of the ec2 instance)
The nginx configuration will still have two server blocks. The 80 server block will be the same as above and will redirect to https.
server {listen 80;
return 301 https://$host$request_uri;
}
The second server block will accept http with no ssl keys or anything and it will have all the configuration in it:
server {
listen 8080;
[All configuration here]
}
The second block will have all the usual configuration BUT you won't need the map or the settings.php stuff. All you will need to use is
fastcgi_params HTTPS on;And that will tell Drupal to treat anything coming in that way to go out with https:// links.
Since you have a second server block for all ssl traffic, you don't need to read the HTTP_X_FORWARDED_PROTO header at all.
And this way the back end server doesn't have to deal with ssl encryption.
Have experieced this
I've dealt with IE8/9 and upgrading to HTTPS using a front-end proxy and rewrite rules. The trick I found was that IE was actually trying to negotiate HTTPS over an HTTP connection and was failing.
The error was:
400 Bad RequestThe plain HTTP request was sent to HTTPS port
nginx
Putting this in the front-end nginx was step one:
server {listen 80;
ssl on;
rewrite ^(.*) https://$host$1 permanent;
error_page 497 https://$host$request_uri;
}
I'm pretty certain it was the error_page 497 that was showing up in the logs that led me to find this common solution to the first error. Firefox and Chrome didn't have any problems. My setup is also using HSTS to force SSL for all visitors, but that only handles returning traffic.
In this instance this fix would handle SSL instead of ELB as dsicussed.
Next up is the proxy_set_header directive (normal):
proxy_set_header X-Forwarded-Proto $scheme;Then in my front-end proxy and backend Drupal fastcgi_params (normal):
fastcgi_param HTTPS on;Hope this helps a bit, even in an ageing thread, or that at least anyone searching for this issue stumbles upon this thread.