I ended up needing to install hitch on a server recently, so the https:// traffic could be routed through Varnish (along with the existing ‘http’ stuff) for performance reasons.
The server only runs WordPress sites, so there are WordPress specific things in the Varnish configuration (vcl) file below.
Versions: Varnish 5.2, Hitch 1.4.4, Apache 2.4 and Debian Jessie.
…
Hitch
Installed via jessie-backports (apt-get install -t jessie-backports hitch)
/etc/hitch/hitch.conf contains :
# Run 'man hitch.conf' for a description of all options. frontend = { host = "*" port = "443" } backend = "[127.0.0.1]:6086" workers = 4 daemon = on user = "_hitch" group = "_hitch" # Enable to let clients negotiate HTTP/2 with ALPN. (default off) (jessie openssl doesn't support this) # alpn-protos = "http/2, http/1.1" # run Varnish as backend over PROXY; varnishd -a :80 -a localhost:6086,PROXY .. write-proxy-v2 = on # Write PROXY header tls-protos = TLSv1.1 TLSv1.2 ocsp-dir = "/etc/hitch/ocsp" ocsp-verify-staple = on pem-file = "/etc/letsencrypt/certs/domain1.com.pem" pem-file = "/etc/letsencrypt/certs/domain2.com.pem" pem-file = "/etc/letsencrypt/certs/domain3.net.pem"
Also :
install -d /etc/hitch/ocsp -o _hitch -g _hitch
Varnish
Installed from PackageCloud repo – instructions etc at https://packagecloud.io/varnishcache/varnish52
I chose version 5.2 as it seemed to be the most recent supported by Jessie, wasn’t the latest-latest (6.0) release which seems potentially too new to use….
Anyway :
/etc/default/varnish contains :
DAEMON_OPTS="-a *:80 -a localhost:6086,PROXY \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s malloc,5G"
(So varnish receives http traffic on port 80, and also listens on port 127.0.0.1:6086 for proxy traffic which is where hitch sends stuff). I’ve told it to use 5GiB of RAM (-s mallow,5G). Change as appropriate.
/etc/varnish/default.vcl contains :
vcl 4.0; import std; # Apache is configured to listen on port 127.0.0.1:81 backend default { .host = "127.0.0.1"; .port = "81"; .max_connections = 100; .first_byte_timeout = 25s; .connect_timeout = 20s; } sub vcl_recv { # Happens before we check if we have this in cache already. # Typically you clean up the request here, removing cookies you don't need, # rewriting the request, etc. unset req.http.proxy; if (req.http.x-forwarded-for) { set req.http.X-Forwarded-For = "" + client.ip + ", " + req.http.X-Forwarded-For ; } else { set req.http.X-Forwarded-For = client.ip; } # If the connection comes from hitch, set the X-Forwarded-Proto HTTP header which Apache will see. if((std.port(local.ip) == 6086) && (std.port(server.ip) == 443)) { set req.http.X-Forwarded-Proto = "https"; } # If it's port 80 (http) traffic for a specific website, redirect to https. if(std.port(local.ip) == 80 && req.http.host ~ "^(?i)example.com$") { set req.http.x-redir = "https://" + req.http.host + req.url; return(synth(301)); } # do not try and cache if it's not a GET/HEAD request. if(req.method != "GET" && req.method != "HEAD") { return(pass); } if (req.http.Cookie) { # We only care about the wordpress login cookies. if (req.http.Cookie ~ "wordpress_logged_in*" ) { std.log("Looks like the user is logged in... " + req.http.Cookie); return (pass); } std.log("Ignoring cookie - " + req.http.Cookie); unset req.http.Cookie; } /* Always cache images and multimedia */ if (req.method == "GET" && req.url ~ "^[^?]*\.(js|css|gif|png|jpg|jpeg|gif|png|tiff|tif|svg|swf|ico|mp3|mp4|m4a|ogg|mov|avi|wmv|woff)(\?.*)$") { unset req.http.Cookie; return(hash); } if(req.http.Authorization || req.http.Cookie) { return (pass); } return (hash); } sub vcl_backend_response { set beresp.grace = 1m; set beresp.keep = 10m; # Do not insert 503 responses into the cache. if (beresp.status == 503 && bereq.is_bgfetch) { return (abandon); } if (bereq.method == "GET" ) { # set large expiry headers for this to improve browser caching. if(bereq.url ~ "\.(gif|jpg|jpeg|bmp|png|tiff|tif|ico|img|tga|wmf|css|js|txt|xml|m4a|ogg|mov|avi|wmv|woff)$") { set beresp.ttl = 86400s; set beresp.http.Cache-Control = "max-age=86400"; unset beresp.http.Expires; unset beresp.http.Pragma; unset beresp.http.ETag; unset beresp.http.Set-Cookie; } } set beresp.grace = 10m; return(deliver); } sub vcl_deliver { # Varnish cache hit HTTP header for debuggin. # Overwrite/remove other headers as desired. set resp.http.Server = "Apache/Varnish"; set resp.http.X-Cache-Hits = obj.hits; unset resp.http.X-Varnish; unset resp.http.Via; return(deliver); } sub vcl_hash { # Note - we hash differently for https requests or if a wordpress user is logged in. hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } if(req.http.Cookie ~ "wordpress_logged_in") { hash_data(req.http.Cookie); } if(req.http.X-Forwarded-Proto == "https") { hash_data(req.http.X-Forwarded-Proto); } return (lookup); } sub vcl_synth { if (resp.status == 301) { set resp.http.Location = req.http.x-redir; return (deliver); } }
Apache
On older versions of Apache, I used the mod_rpaf module so REMOTE_ADDR and HTTPS ($_SERVER[‘REMOTE_ADDR’] or $_SERVER[‘HTTPS’] for PHP) were rewritten to pretend that Apache received the traffic directly.
In Jessie, mod_rpaf is marked as being deprecated and replaced with mod_remoteip – so….
a2enmod remoteip
and add
RemoteIPHeader X-Forwarded-For
to your Apache configuration.
While you can also add :
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
note it will ‘fix’ PHP to see that HTTPS is enabled (or WordPress’s is_ssl() call). So that’s good.
But it won’t fix mod_rewrite rules that check HTTPS – so if you’re using something like :
RewriteCond %{HTTPS} on
You’ll need to replace it with
RewriteCond %{HTTP:X-Forwarded-Proto} https [NC]
That’s a bit annoying but oh well.
LetsEncrypt
When your LetsEncrypt certificates renew, you should just need to kill -HUP hitch, or just call /etc/init.d/hitch force-reload