I am starting to use Aegir in containers with the NGINX-Proxy plus LetsEncrypt companion container.
This setup is similar to using load balancers with HTTPS termination: The request for HTTPS hits the NGINX proxy, then it's HTTP only requests through to the Drupal site.
The problem is that Drupal detects an HTTP request, and then renders all links to content like CSS and javascript to HTTP, causing problems.
I learned that Drupal looks for $_SERVER['HTTPS'] = 'on'
to determine what scheme to use, so all we have to do is detect the HTTP_X_FORWARDED_PROTO and set "HTTPS" = on to automatically let Drupal handle the different scheme.
Let's put this directly in the settings.php templates:
/**
* If external request was HTTPS but internal request is HTTP, set $_SERVER['HTTPS'] so Drupal detects the right scheme.
*/
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' && $_SERVER["REQUEST_SCHEME"] == 'http') {
$_SERVER['HTTPS'] = 'on';
}
Comment | File | Size | Author |
---|---|---|---|
#26 | provision-support_https_proxies-3040646-26.patch | 3.99 KB | llamech |
Comments
Comment #2
Jon PughComment #3
Jon PughComment #6
Jon PughComment #7
colanI'm not in a good position to test this right now, but I did something similar with an external (to Aegir) HTTPS proxy so that Drupal sites know about it. See the example I added to Using a load balancer or reverse proxy.
Not sure about D6, but for D7 I set the
$base_url
. In D8, I didn't even have to do that. In both cases, I didn't need to explicitly enable HTTPS so maybe it figured this out from the proxy? So you probably just have to set that.Comment #8
ergonlogicShouldn't we set this in the vhost templates, rather than in
settings.php
?Comment #9
Jon PughThis method seems much simpler, Colan.
Drupal 6 7 and 8 all look at that $_SERVER['HTTPS'] variable.
Since all 3 set the base_url dynamically based on $_SERVER['HTTPS'], we simply change that to "on" and Drupal handles the rest.
ergonlogic: I don't see the benefit. settings.php method will work for NGINX also, and it only kicks in if the original request is in HTTPS. Perhaps it could be done in Apache Vhosts with Regex? but I'm sure it is simpler and less code to maintain with these 4 lines in settings.php
Comment #10
colanBut Drupal still needs to know the proxy IP addresses. Where are you setting those?
Comment #11
Jon PughIn my testing, it isn't a requirement for it to work, but I suppose if we can, we should.
REMOTE_ADDR should probably be set too.
Comment #12
ergonlogicIt'd be good to remain consistent. We already set that param here, along with lots of other headers.
Both Nginx and Apache provide simple conditional syntax:
Comment #13
helmo CreditAttribution: helmo at Initfour websolutions commented+1 for setting the var
@ergonlogic, adding it in the apache/nginx templates looks more complex .. and note that with the use of an external SSL termination the ssl variant of these templates is often not used, ss it would have to land in both ssl and non-ssl variants.
Comment #14
Jon Pughergonlogic: The "HTTPS" param is only set where you mention when the server is using hosting_ssl or hosting_https. What I am talking about is when those modules are not be used at all.
We'd have to see a patch to the NGINX and Apache templates to do a direct comparison, but on initial count, 6 files would have to be changed to implement on the vhost config level: (core vhost.tpl.php, vhost_ssl.tpl.php and vhost_https.tpl.php for both nginx and Apache)
Keeping this logic in PHP is less code, less files, less obfuscation (a simple if statement versus regex).
It's also more clear to Aegir users. Settings.php is the first place Aegir users would look, and a local.settings.php file is the easiest way to override this behavior.
Considering no one has marked it "Needs Work", I assume yall don't feel to strongly against this. Let's move forward with this simple change?
Comment #15
memtkmcc CreditAttribution: memtkmcc at Omega8.cc commentedThe
$_SERVER['HTTPS']
belongs in settings.php (PHP level) not in the vhosts (web server level) and is pretty harmless addition as it affects only proxied requests with external HTTPS termination.Both Jon and helmo are correct that it can't even be done on the vhost level, because vhost is not aware of external HTTPS termination, only aware of local request which is plain HTTP.
Also, the
ssl on;
Nginx directive is not the same as$_SERVER['HTTPS']
and is already deprecated in newer Nginx releases, plus, even if Nginx offers theif()
logic (which should be avoided like plague btw), many/most directives can't be put withinif()
anyway.Comment #16
memtkmcc CreditAttribution: memtkmcc at Omega8.cc commentedI should also mention that we are using similar logic in the
global.inc
in BOA to make HTTPS support more predictable for many years already.Comment #17
memtkmcc CreditAttribution: memtkmcc at Omega8.cc commentedComment #18
memtkmcc CreditAttribution: memtkmcc at Omega8.cc commentedFor reference, so we don't overlook this later:
https://stackoverflow.com/questions/51703109/nginx-the-ssl-directive-is-...
https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-s...
https://nginx.org/en/docs/http/configuring_https_servers.html
Comment #19
ergonlogicI respectfully disagree. PHP expects $_SERVER to be set by the web server: http://php.net/manual/en/reserved.variables.server.php
No regex or other complex logic should be required in the vhost to accomplish this. Setting a header in response to another header's value is common practice.
While it would have to be set in the non-SSL vhosts selectively, it should be set by default in the SSL ones.
As it stands, we're making changes to how we pass this into each version of Drupal. We'll presumably need to hack this into Drupal 9+ as well.
Since we're doing this in PHP, other apps (such as JS running on hosted sites) won't have access to these modified headers.
Anyway, I'll stand aside. I still disagree with the implementation, but I won't block it.
Comment #20
memtkmcc CreditAttribution: memtkmcc at Omega8.cc commentedIf you can propose a working modification to support sending proper variable to PHP (FPM and Apache) level (Drupal) from the web server level, based on received (not set) HTTP headers, why not?
Comment #21
ergonlogicokay, I'll take a crack at this.
Comment #22
Jon PughFWIW I do think we do need a much better way to set generic server variables per site. I've gotten feature requests for devshop to do just that. If that existed this feature could leverage that.
Also worth noting that drushrc.php sets $_SERVER right now. Provision should have an easy and generic way to set the environment for both the web server and the CLI on a per site basis.
Provision 4 does this for CLI users with a special Shell command that sets the Drupal environment vars in a hard coded way so that drush will work: https://github.com/provisionCLI/provision/blob/4.x/src/Provision/Command...
It's still very manual.
We should look at something like the
load.environment.php
in the drupal-composer project, which leverages Dotenv library to automatically load .env files.https://github.com/drupal-composer/drupal-project/blob/8.x/load.environm...
Comment #23
ergonlogicHere's a patch for the Nginx piece. It's about 4x longer than it should be, IMO, but
subdirs.tpl.php
reproduces thefastcgi_param
blocks multiple times for some reason.Comment #25
llamechFixes pushed to
dev/3040646-https-proxy
Test procedure:
3040646-https-proxy
branch.local.settings.php
:"off"
.X-Forwarded-Proto
header in ModHeader to"foo"
and refresh the site; confirm HTTPS header is still not being set to"on"
."https"
and refresh the site; confirm that HTTPS header is now being set to"on"
.Comment #26
llamechFor ease of review, here's a patch.
Comment #29
ergonlogicFWIW, this is currently only matching "https". If a proxy is adding a
X-Forwarded-Proto
header with "HTTPS" or "Https" (for example) it won't match. I don't know whether this is likely. We can always match against an array instead.Comment #30
ergonlogicDeploying this requires re-verifying Nginx servers, since it sets a variable in the server vhost that's then used in site vhosts. I suppose a
hook_update_N()
will be required to trigger this automatically. Should that be inhosting_nginx
? I don't think it's required on Apache.Comment #31
colanBranch works in my tests, but we still need that hook update.
Comment #32
colanMoving projects as the hook needs to be in Hosting. We can track both from Hostmaster.
Comment #33
ergonlogicI pushed a commit to a new `3040646-https-proxy` branch in Hosting that adds a
hook_update_N()
, to regenerate the Nginx server-wide vhosts.
Comment #34
colanWith a clean dev VM, and pulling the branch here, a verify task doesn't get created from the command line with:
drush @hm updb
..but it does work from the front-end by hitting
/update.php
.Can someone else confirm?
Comment #35
Jon PughSo now there are 3 separate branches for this issue...
Keeping this in settings.php is the best for long term maintenance. The other branches require maintaining logic flow structures in both Apache configs and Nginx configs, while settings.php method is a single if statement regardless of host.
I don't want this issue to be derailed. Comment #17 approved this change, so I am merging.
Comment #36
Jon PughComment #39
Jon PughRelated followup:
Aegir Ansible module provides Apache servers with HTTPS only. It must work without HTTPS as well.