Nginx interacts with the backend (PHP) through http, not https so you need to tell Drupal explicitly when you're using https in settings.php
Nginx interacts with the backend (PHP) through http, not https so you need to tell Drupal explicitly when you're using https in settings.phpSaturday, 26th Jan 2013
This is a little "gotcha" that I came across recently. Back when I knew essentially nothing about configuring servers beyond working in cPanel and uploading .htaccess files to shared hosts my standard tactic to build a new server that wasn't pre-configured was to run the Barracuda bash script and roll with whatever came out the other end. It was actually a pretty successful tactic for a while as it gave me a lot of breathing space to start learning my way around linux without worrying that some gap in my knowledge would almost certainly lead to catastrophic crashes or data loss on our servers.
I'm still far from a linux guru but these days I can put together an Aegir dev environment running on nginx or apache in pretty short order and do some basic configuration changes on prod without blowing everything up.
For a long time I've been adapting this PHP snippet provided by the Omega8 crew to handle https redirects when Drupal sits behind nginx:
// Kind of core version agnostic, securepages module
// for proper HTTP/HTTPS redirects.
if (isset($_SERVER['HTTP_HOST']) && preg_match("/(?:domain\.com|another-domain\.com)/", $_SERVER['HTTP_HOST']) &&
isset($_SERVER['REQUEST_URI']) && isset($_SERVER['HTTP_USER_AGENT'])) {
$request_type = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ? 'SSL' : 'NONSSL';
$conf['https'] = TRUE;
if (preg_match("/^\/(?:cart.*|checkout.*|admin.*|donate.*|civicrm.*|node\/add.*|node\/.*\/edit)$/", $_SERVER['REQUEST_URI']) ||
preg_match("/^\/(?:user.*|user\/.*\/edit.*|user\/reset.*|user\/register.*|user\/logout|user\/password|user\/login)$/", $_SERVER['REQUEST_URI'])) {
$base_url = 'https://' . $_SERVER['HTTP_HOST'];
if ($request_type != "SSL") {
header('X-Accel-Expires: 1');
// Note: never use header('X-Accel-Expires: 0'); to disable Speed Booster completely.
// You always want that one second or you will be vulnerable to DoS attacks.
header("HTTP/1.1 301 Moved Permanently");
header("Location: https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
}
else {
$base_url = 'http://' . $_SERVER['HTTP_HOST'];
if ($request_type == "SSL" && !preg_match("/(?:x-progress-id|ahah|filefield_nginx_progress\/*|tinybrowser|f?ckeditor|tinymce|autocomplete|ajax|batch|js\/.*)/", $_SERVER['REQUEST_URI'])) {
header('X-Accel-Expires: 1');
// Note: never use header('X-Accel-Expires: 0'); to disable Speed Booster completely.
// You always want that one second or you will be vulnerable to DoS attacks.
header("HTTP/1.1 301 Moved Permanently");
header("Location: http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
}
}
Recently though, I wanted to handle the http -> https redirect at the nginx level as it should be generally faster and more robust, so I dropped this snippet from my settings.inc
added some extra rules to nginx.conf
. The redirection was working fine but Drupal was rendering some urls rather incorrectly - most noticeably all of my linked stylesheets were being referenced by http (not https) urls which led to Chrome blocking them and throwing "insecure content" warnings or just entirely broken urls.
There were a few vital pieces of information that I was missing in my config:
- Regardless of whether nginx uses https or http to communicate with your browser, it is probably configured to always communicate with PHP in http when dynamic pages are requested. As I understand, this is largely a performance consideration.
- When you communicate with PHP in http, Drupal will initialize the
$base_url
variable with http - which has a flow-on effect to just about every url generated by the system. - Drupal also has a
$conf['https']
variable that can be explicitly set toTRUE
insettings.php
to help ensure consistent behaviour across http/https as required. - The Omega8/Barracuda snippet that I pasted above doesn't just perform redirects, it overrides the
$base_url
variable and sets config for the current scheme used by the browser rather than the current scheme used by PHP (which is always http).
How to tell the difference between the current scheme (always http) and the scheme used by the browser? Use the $_SERVER['HTTP_X_FORWARDED_PROTO']
global variable in PHP, which is also $http_x_forwarded_proto
in nginx.conf
files.
Once you've determined whether the current request from the browser is https or http you need to add (at least) the following snippet to settings.php
to force Drupal to behave. This snippet was tested in Drupal 6.
// I sure hope this is set..
if (isset($_SERVER['HTTP_HOST'])) {
// Updated our $base_url global and https config if
// the forwarded protocol requires it.
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
$base_url = 'https://' . $_SERVER['HTTP_HOST'];
$conf['https'] = FALSE;
}
else {
$base_url = 'http://' . $_SERVER['HTTP_HOST'];
}
}
This is quite a bit simpler than the Omega8 offering and focuses only on handling the minimum required Drupal configuration assuming that the required https/http redirection has already been performed at the server level.
This snippet should work equally well when Drupal is sitting behind any kind of proxy or load balancer that sends http requests to PHP but accepts https requests from the end-user.
Syndicate: planet drupal