When moving some sites to a new server I upgraded the Varnish cache server configuration serving this setup. Here are my notes how one can use Varnish at the front of virtual hosting.
The setup is following
- Multiple sites are hosted on the same server. The sites are mix of PHP of Plone sites.
- Varnish accepts HTTP requests in port 80 and forwards request to the corresponding backend through HTTP proxying
- Our virtual host rules capture domain names with or without www-prefix, or with any subdomain name prefix
- Apache runs in non-standard port localhost:81, serving PHP and WordPress. WordPress caching rules are defined in Apache <virtualhost> config file.
- Every Plone site runs in its own port and process. Plone uses VirtualHostMonster to rewrite publicly facing site URLS. Plone caching HTTP headers are set by plone.app.caching addon.
- We do extensive cookie sanitization for PHP (generic), WordPress and Plone. Google Analytics etc. cookies don’t bust the cache and we can still login to WordPress and Plone as admin
- As a special trick, there is cleaned cookie debugging trick through HTTP response headers
Pros
- Blazingly fast, as Varnish is
- With Plone’s plone.app.caching, one does not need to touch configuration files but Plone caching HTTP headers can be configured through-the-web
Cons
- Varnish does not have Nginx or Apache style virtual host configuration file facilities by default and making includes is little bit tricky: With many virtualhost the default.vcl config file grows long.
- Because WordPress cannot do static resource serving as smartly as Plone, which has unique URLs for all static media revisions, you need to purge Varnish manually from command line if you update any static media files like CSS, JS or images.
Varnish /etc/varnish/default.vcl example for Varnish 3.0:
# # This backend never responds... we get hit in the case of bad virtualhost name # backend default { .host = "127.0.0.1"; .port = "55555"; } backend myplonesite { .host = "127.0.0.1"; .port = "6699"; } # # Apache running on server port 81 # backend apache { .host = "127.0.0.1"; .port = "81"; } # # Gues which site / virtualhost we are diving into. # Apache, Nginx or Plone directly # sub choose_backend { # WordPress site if (req.http.host ~ "^(.*\.)?opensourcehacker\.com(:[0-9]+)?$") { set req.backend = apache; } # Example Plone site if (req.http.host ~ "^(.*\.)?myplonesite\.fi(:[0-9]+)?$") { set req.backend = myplonesite; # Zope VirtualHostMonster set req.url = "/VirtualHostBase/http/" + req.http.host + ":80/Plone/VirtualHostRoot" + req.url; } } sub vcl_recv { # # Do Plone cookie sanitization, so cookies do not destroy cacheable anonymous pages. # Also, make sure we do not destroy WordPress admin and login cookies in the proces # if (req.http.Cookie && !(req.url ~ "wp-(login|admin)")) { set req.http.Cookie = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(statusmessages|__ac|_ZopeId|__cp|php|PHP|wordpress_(.*))=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.Cookie == "") { remove req.http.Cookie; } } call choose_backend; if (req.request != "GET" && req.request != "HEAD" && req.request != "PUT" && req.request != "POST" && req.request != "TRACE" && req.request != "OPTIONS" && req.request != "DELETE") { /* Non-RFC2616 or CONNECT which is weird. */ return (pipe); } if (req.request != "GET" && req.request != "HEAD") { /* We only deal with GET and HEAD by default */ return (pass); } if (req.http.Authorization || req.http.Cookie) { /* Not cacheable by default */ return (pass); } return (lookup); } sub vcl_fetch { /* Use to see what cookies go through our filtering code to the server */ /* set beresp.http.X-Varnish-Cookie-Debug = "Cleaned request cookie: " + req.http.Cookie; */ if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { /* * Mark as "Hit-For-Pass" for the next 2 minutes */ set beresp.ttl = 120 s; return (hit_for_pass); } return (deliver); } # # Show custom helpful 500 page when the upstream does not respond # sub vcl_error { // Let's deliver a friendlier error page. // You can customize this as you wish. set obj.http.Content-Type = "text/html; charset=utf-8"; synthetic {" <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>"} + obj.status + " " + obj.response + {"</title> <style type="text/css"> #page {width: 400px; padding: 10px; margin: 20px auto; border: 1px solid black; background-color: #FFF;} p {margin-left:20px;} body {background-color: #DDD; margin: auto;} </style> </head> <body> <div id="page"> <h1>Sivu ei ole saatavissa</h1> <p>Pahoittelemme, mutta palvelua ei ole saatavilla.</p> <hr /> <h4>Debug Info:</h4> <pre>Status: "} + obj.status + {" Response: "} + obj.response + {" XID: "} + req.xid + {"</pre> </div> </body> </html> "}; return(deliver); }
WordPress does not support setting HTTP response headers natively like Plone. We set them in Apache virtual host configuration file in /etc/apache2/sites-enabled:
<VirtualHost 127.0.0.1:81> ServerName opensourcehacker.com ServerAlias www.opensourcehacker.com ServerAdmin mikko@opensourcehacker.com LogFormat combined TransferLog /var/log/apache2/opensourcehacker.com.log # Basic WordPress setup Options +Indexes FollowSymLinks +ExecCGI DocumentRoot /srv/php/opensourcehacker/wordpress <Directory /srv/php/opensourcehacker/wordpress> Options FollowSymlinks AllowOverride All </Directory> AddType text/css .css AddType application/x-httpd-php .php .php3 .php4 .php5 AddType application/x-httpd-php-source .phps # # Set expires headers manually # ExpiresActive On ExpiresByType text/html A0 ExpiresByType image/gif A3600 ExpiresByType image/png A3600 ExpiresByType image/image/vnd.microsoft.icon A3600 ExpiresByType image/jpeg A3600 ExpiresByType text/css A3600 ExpiresByType text/javascript A3600 ExpiresByType application/x-javascript A3600 </VirtualHost>
Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+