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')

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:

  1. Disable SSL entirely, including for the admin interface. This is the least preferred option, but at least it keeps things working consistently (albeit insecurely).
  2. 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?

Solutions Collecting From Web of "Separating HTTP and HTTPS content with WordPress, Varnish, and an SSL terminator?"

I know this is a rather old question but I’ll leave my findings in case someone else needs to make this work.

Your approach helped me implement a similar setup.

In order for Varnish to keep a separate cache for http and https versions of the same page you simply need to add the X-Forwarded-Proto header in the hash_data() function in vcl_hash.

Like this:

sub vcl_hash {
        if ( {
        } else {

        # Keep separate caches if https is used

This way a user visiting will get the cache for the http version while a user visiting will get the cache for the https version.

You may want to make it more specific so it will only keep double caches for the html content and not images, css, js, etc which are usually the same regardless of the protocol used.

There should not be a problem unless you have some misconfiguration or misbehaving theme or plugin. WordPress core sends an http header that instructs caching servers to not cache the content being sent whenever the page is accessed by a logged-in user.

For reference the headers are set at wp_get_nocache_headers and sent from send_headers, and from a 2 min code review right now the code seems very solid to me.