Separating HTTP and HTTPS content with WordPress, Varnish, and an SSL terminator?
Background: I host a WordPress site with a hosting company that places a combined Varnish server + SSL terminator system upstream of my web server. The WordPress site runs on Apache and is accessible over both HTTP and HTTPS via the Varnish+SSL terminator.
The setup looks like this:
Image credit: DigitalOcean (For reference, DigitalOcean is not my host but their image precisely describes the setup at my host.)
I do not have administrative access to the Varnish+SSL system.
All the local traffic between the SSL terminator, Varnish, and my web server are unencrypted.
Objective: I wish to have the WordPress admin interface accessible only over a secure (HTTPS) connection. Users should be able to access the WordPress site over HTTP or HTTPS at their choosing, with the default being HTTP.
Relevant Configuration: Since the web server itself does not know if the initial connection was secure, WordPress goes into a redirect loop if I force the admin interface to be accessible only over HTTPS using define('FORCE_SSL_ADMIN', true);
in my wp-config.php
file.
However, the Varnish+SSL terminator system does include headers to indicate what protocol was used, so I am using the following working configuration:
define('FORCE_SSL_ADMIN', true);
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
$_SERVER['HTTPS']='on';
Thus, WordPress will automatically recognize if a visitor is accessing the site over HTTP or HTTPS and will modify links to CSS/JavaScript files appropriately to avoid "insecure content" warnings.
Problem: Although WordPress will modify links based on protocol to prevent "insecure content" warnings, Varnish doesn't care about protocols and will cache content (regardless of protocol) until it expires.
As an example, if Varnish starts with an empty cache and the first visitor is using HTTPS, WordPress modifies all the links to refer to HTTPS and returns the requested content. Varnish caches this content with the HTTPS links. If the second visitor connects using HTTP, Varnish returns the cached content with HTTPS links and the visitor loads CSS/JavaScript over HTTPS. If the user clicks any links to other content on the site, they access that content over HTTPS.
However, if we reverse the situation (that is, the first visitor uses HTTP and the second visitor uses HTTPS), a problem arises for the second visitor: the cached content instructs the visitor to load the CSS/JavaScript over HTTP. Browsers refuse to do this for security reasons, the browser shows an "insecure content" warning, and none of the formatting/scripts work.
Attempted Solutions:
- Disable SSL entirely, including for the admin interface. This is the least preferred option, but at least it keeps things working consistently (albeit insecurely).
Redirect all non-admin traffic to HTTP with the following .htaccess rule:
RewriteCond %{HTTP:X-Forwarded-Proto} =https RewriteRule !^wp-(admin|login|register)(.*) - [C] RewriteRule ^(.*)$ http://%{SERVER_NAME}/$1 [L]
This would work if not for Varnish: if the visitor accesses a non-admin/login/register URL at the site over HTTPS, Apache does a rewrite to point the visitor at the HTTP version of the content. Varnish caches the redirect without caring about which protocol is used, so that visitor and all subsequent visitors get stuck in a rewrite loop. Even if it worked, this method is less than ideal because it forces all non-admin users to access the site over HTTP only -- I want to allow users to be able to access the content using HTTP or HTTPS, at their choice.
Question: Without having admin access to the Varnish+SSL system, is there some way of resolving this problem so that users accessing the site over HTTP and HTTPS don't tread on each other's caches?