9

Debian : nginx + https + Varnish4 + HHVM

Depuis le passage du blog en full HTTPS avec l’aide de Let’s Encrypt, j’avais laissé tombé l’utilisation de Varnish car ce dernier n’était pas compatible avec le protocole HTTPS.

Cependant, je n’avais pas encore découvert toute la puissance de Nginx, qui était utilisé jusqu’à présent en tant que serveur web (et il le fait très bien). Effectivement, Nginx va être configuré en tant que serveur web et revers proxy. Et c’est via la fonction de revers proxy, que je vais pouvoir utiliser Varnish et avoir un serveur en béton armer contre les risques d’erreur 503 dû à un pique d’influence !

Voici le schéma de principe :
memolinux-webninx

Installation de Nginx

Pour installer et configurer Nginx et HHVM sur un serveur Debian, voir l’article : Installer un serveur web hyper performant LNHM

Installation de Varnish 4

Pour faire fonctionner Varnish dérrière le revers proxy Nginx, il faut passer par l’installation du paquet apt-transport-https.
Pour l’installation de Varnish, il est possible de passer par les dépôts de Debian car le paquet varnish y est présent mais dans version 4.0 et je souhaite utiliser la dernier version disponible de varnish, qui est 4.1 :

  • Ajout du dépot de varnish :
  • echo "deb https://repo.varnish-cache.org/debian/ jessie varnish-4.1" >> /etc/apt/sources.list.d/varnish-cache.list
  • Ajout de la clé :
  • curl https://repo.varnish-cache.org/GPG-key.txt | apt-key add -
  • Mise à jour des dépôts :
  • apt update
  • Instalationn de Varnish 4.1 et de apt-transport-https :
  • apt install apt-transport-https varnish

Configuration de Nginx

Dans mon cas , Nginx sera à la fois utiliser en revers proxy et serveur web.
Voici mon block server du blog :

nano /etc/nginx/site-enabled/blog
server {
listen 127.0.0.1:8088;
server_name memo-linux.com;
#rewrite      ^ https://memo-linux.com$request_uri?;
root /var/www/memo/;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
           }
}
server {
listen 1443 ssl http2;
server_name memo-linux.com;

access_log /var/log/nginx/memolinux-access.log;
error_log /var/log/nginx/memolinux-error.log;

add_header X-Content-Type-Options nosniff;

ssl_certificate /etc/letsencrypt/live/memo-linux.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/memo-linux.com/privkey.pem;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam /etc/ssl/private/dhparams.pem;
ssl_buffer_size 16k;
#ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/memo-linux.com/fullchain.pem;
resolver 8.8.4.4 8.8.8.8;
root /var/www/memo/;
index index.php;
  # Process only the requests to wp-login and wp-admin


location ~ /\.ht {
    deny all;
}
location / {
   proxy_pass http://127.0.0.1:80;
   proxy_read_timeout    90;
   proxy_connect_timeout 90;
   proxy_redirect        off;

   proxy_set_header      X-Real-IP $remote_addr;
   proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header      X-Forwarded-Proto https;
   proxy_set_header      X-Forwarded-Port 1443;
   proxy_set_header      Host $host;
        }
include /etc/nginx/hhvm.conf;
include global/security.conf;

location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff)$ {
                expires max;
                log_not_found off;
add_header Pragma public;
    add_header Cache-Control "public";
}

location ~ /wp-admin {
    auth_basic           "closed site";
    auth_basic_user_file /var/www/.htpasswd;
}
}

Vérification et relance de nginx :

nginx -t
systemctl restart nginx

Configuration Varnish 4

Avant d’aller plus loin, il faut forcer varnish à écouter sur le port 80, voici la démarche : https://memo-linux.com/varnish-4-forcer-lecoute-sur-le-port-80-sous-debian-8/
Mon fichier default.vcl :

nano /etc/varnish/default.vcl
vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend autresite {
        .host = "127.0.0.1";
        .port = "8080";
        .connect_timeout = 600s;
        .first_byte_timeout = 600s;
        .between_bytes_timeout = 600s;
        .max_connections = 800;
}

backend memo {
        .host = "127.0.0.1";
        .port = "8088";
        .connect_timeout = 600s;
        .first_byte_timeout = 600s;
        .between_bytes_timeout = 600s;
        .max_connections = 800;

}
import std;
include "xforward.vcl";
include "bigfiles.vcl";
include "static.vcl";
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.

    if (req.http.host ~ "memo-linux.com") {
        set req.backend_hint = memo;
    } elseif (req.http.host ~ "autresite.tld") {
        set req.backend_hint = autresite;
    }
# ---------------------- WORDPRESS SPECIFIC CONFIG -------------------- #
        #previwew mode
        if (req.url ~ "preview=true") {
        return(pass);
        }
        # Did not cache the RSS feed
        if (req.url ~ "/feed") {
        return (pass);
        }
        # Blitz hack
        if (req.url ~ "/mu-.*") {
        return (pass);
        }
        # Did not cache the admin and login pages
        if (req.url ~ "/wp-(login|admin)") {
        return (pass);
        }

# Remove the "has_js" cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
        # Remove any Google Analytics based cookies
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
        # Remove the Quant Capital cookies (added by some plugin, all __qca)
        set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
        # Remove the wp-settings-1 cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");
        # Remove the wp-settings-time-1 cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");
        # Remove the wp test cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
        # remove cookies for comments cookie to make caching better.
        set req.http.cookie = regsub(req.http.cookie, "dcd9527364a17bb2ae97db0ead3110ed=[^;]+(; )?", "");

        # remove ?ver=xxxxx strings from urls so css and js files are cached.
        set req.url = regsub(req.url, "\?ver=.*$", "");
        # Remove "replytocom" from requests to make caching better.
        set req.url = regsub(req.url, "\?replytocom=.*$", "");
        # Strip hash, server doesn't need it.
        set req.url = regsub(req.url, "\#.*$", "");
        # Strip trailing ?
        set req.url = regsub(req.url, "\?$", "");
# Are there cookies left with only spaces or that are empty?
        if (req.http.cookie ~ "^ *$") {
        unset req.http.cookie;
        }
        # Drop any cookies sent to WordPress.
        if (!(req.url ~ "wp-(login|admin)")) {
                       unset req.http.cookie;
        }
        # Cache the following files extensions
        if (req.url ~ "\.(css|js|png|gif|jp(e)?g|swf|ico)") {
        unset req.http.cookie;
        }

        # Normalize Accept-Encoding header and compression
        # https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
        if (req.http.Accept-Encoding) {
        # Do no compress compressed files...
        if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
        unset req.http.Accept-Encoding;
        } elsif (req.http.Accept-Encoding ~ "gzip") {
        set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
        set req.http.Accept-Encoding = "deflate";
        } else {
        unset req.http.Accept-Encoding;
        }
        }

        # Check the cookies for wordpress-specific items
        if (req.http.Cookie ~ "wordpress_" || req.http.Cookie ~ "comment_") {
        return (pass);
        }
        if (!req.http.cookie) {
        unset req.http.cookie;
        }
        # ---------------------- /WORDPRESS SPECIFIC CONFIG -------------------- #
 # No cache for big video files
        if (req.url ~ "\.(avi|mp4)") {
                return (pass);
        }

        # Do not cache HTTP authentication and HTTP Cookie
        if (req.http.Authorization || req.http.Cookie) {
        # Not cacheable by default
        return (pass);
        }
        # Cache all others requests
        return (hash);
sub vcl_backend_response {
    # Happens after we have read the response headers from the backend.
    #
    # Here you clean the response headers, removing silly Set-Cookie headers
    # and other mistakes your backend does.

    # Drop any cookies WordPress tries to send back to the client.
    if (!(bereq.url ~ "wp-(login|admin)")) {
                       unset beresp.http.set-cookie;
            }

}
sub vcl_deliver {
    # Happens when we have all the pieces we need, and are about to send the
    # response to the client.
    #
    # You can do accounting or modifying the final object here.

        if (obj.hits > 0) {
        set resp.http.X-Cache = "cached";
        } else {
        set resp.http.x-Cache = "uncached";
        }

        # Remove some headers. Pendant les tests laisser commenté les lignes unset
       unset resp.http.X-Powered-By;
       unset resp.http.X-Varnish;
       unset resp.http.Via;
       unset resp.http.Age;
       unset resp.http.Link;
       unset resp.http.Server;
        return (deliver);
}

sub vcl_pipe {
        # Note that only the first request to the backend will have
        X-Forwarded-For set. If you use X-Forwarded-For and want to
        # have it set for all requests, make sure to have:
        set bereq.http.connection = "close";
        # here. It is not set by default as it might break some broken web
        return (pipe);
}

# The data on which the hashing will take place
sub vcl_hash {
        hash_data(req.url);
        if (req.http.host) {
                hash_data(req.http.host);
        } else {
                hash_data(server.ip);
        }
        # hash cookies for requests that have them
        if (req.http.Cookie) {
                hash_data(req.http.Cookie);
        }
}
sub vcl_hit {
        return (deliver);
}
sub vcl_miss {
        return (fetch);
}
sub vcl_init {
        return (ok);
}
sub vcl_fini {
        return (ok);
}

Mon fichier bigfiles.vcl :

sub vcl_backend_response {
# Bypass cache for files > 10 MB
if (std.integer(beresp.http.Content-Length, 0) > 10485760) {
 set beresp.uncacheable = true;
        set beresp.ttl = 120s;
        return (deliver);
}
}

mon fichier static.vcl :

# static.vcl -- Static File Caching for Varnish
#
sub vcl_recv {
        if (req.method ~ "^(GET|HEAD)$" && req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|$
                if (req.url ~ "nocache") {
                        return(pass);
                }
                set req.url = regsub(req.url, "\?.*$", "");
                unset req.http.Cookie;
                return(hash);
        }

                # Added security, the "w00tw00t" attacks are pretty annoying so lets block it before it reaches our webserver
        if (req.url ~ "^/w00tw00t"){
             return( synth(403, "not permitted !"));
        }
}

sub vcl_backend_response {
        if (bereq.method ~ "^(GET|HEAD)$" && bereq.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|$
                unset beresp.http.set-cookie;
                set beresp.ttl = 24h;
                set beresp.grace = 5m;
        }

        ###
        if (beresp.http.content-type ~ "text") {
              set beresp.do_gzip = true;
        }

        # set minimum timeouts to auto-discard stored objects
        set beresp.grace = 5m;
        # no cache for error pages
        if ( beresp.status == 404 || beresp.status >= 500 ) {
                set beresp.ttl = 0s;
                return(deliver);
        }
        # Set 2min cache if unset for static files
        if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {
                set beresp.ttl = 120s;
                return (deliver);
        }
        # If WordPress cookies found then page is not cacheable

      if (bereq.http.Cookie ~"(wp-postpass|wordpress_logged_in|comment_author_)") {
                set beresp.ttl = 0s;
                return(deliver);
        }
        # don't cache search results
        if( bereq.url ~ "\?s=" ){
                 return(deliver);
        }

        return (deliver);
}

Mon fichier xforward.vcl :

sub vcl_recv {
 if (req.restarts == 0) {
 if (req.http.X-Forwarded-For) {
 set req.http.X-Forwarded-For =
 req.http.X-Forwarded-For + ", " + client.ip;
 } else {
 set req.http.X-Forwarded-For = client.ip;
 }
 }
}

Pour les fichier .vcl , je me suis inspiré de ce site : https://ww.skyminds.net/serveur-dedie-installer-configurer-varnish-4/

Relance de varnish :

systemctl restart varnish

Pour vérifier que le cache de varnish fonctionne, on peut utliser la commande curl :
curlmemolinux

Benchmark

Un petit tour sur le site blitz.io :
memolinux-varnish4

Autre test avec la commande h2load :
h2load

Conclusion

Franchement varnish ça roxe du poney !
Cependant, j’ai deux soucis :

  • Problème sur la mise en forme des lignes de code.
  • Je ne peux plus avoir de preview des articles en cours de rédaction et c’est assez gênant.

Voilà, je continu à chercher la résolution aux problèmes, si toutes fois un lecteur à la solution ou une piste, merci de l’indiquer dans les commentaires :-)

Partager l'article :





fred

"Dire que l'on s'en fiche du droit à la vie privée sous prétexte qu'on a rien à cacher, c'est comme déclarer que l'on se fiche du droit à la liberté d'expression sous prétexte qu'on a rien à dire." Edward Snowden

9 commentaires

  1. Salut,
    Je n’ai peut être pas compris ton problème mais pourquoi tu n’utilises pas simplement nginx avec reverse proxy+cache et cache fastCGI (puisqu’il sait aussi faire ça… même si moins d’options qu’avec varnish) ?

    En gros, tu auras par exemple :
    – nginx en 8080 qui fait du fastcgi cache avec HHVM.
    – nginx en 80/443 qui fait du reverse proxy+cache vers le nginx qui écoute en 8080.

    C’est plus simple et plus facile à maintenir… et ça consomme moins de ressources car y’a moins de processus sur ton serveur.

  2. Le but initial était de relever un défis, faire fonctionner varnish sur un serveur en https :-)
    mais bon, je ne sais pas si c’est vraiment pertinent au final… Je vais laisser comme ça un certain temps et si je ne vois pas plus d’avantage je repartirais sur la conf d’origine sans varnish…

  3. Pour la preview des articles, tu dois pouvoir la retrouver en ajoutant une règle, dans le recv, dans le genre :

    if (req.url ~ preview) {
    return (pass);
    }

    En prenant soin de bien repérer l’uri qui permet d’avoir la preview. Ça devrait marcher :)

  4. J’ai le tour avec des Drupal au taf pour le backoffice : tu n’as pas le choix, il faut exclure tout ce qui à trait à wp-admin de Varnish. En soit, c’est pas la mort, si t’es le seul à éditer les articles :D Et encore, c’est pas garanti que ça fonctionne à 100%. mais c’est un début.

  5. Merci dada mais ça ne fonctionne pas :(
    j’avais déjà mis ce code juste en dessous de ###WORDPRESS SPECIFIC CONFIG
    @seboss666 je n’ai pas de souci pour accéder au tableau de bord de wordpress, juste le problème sur les preview…
    En tout cas c’est sympa de m’aider :-D

    Je vais refaire la conf en partant de zéro sur mon 2ème VPS , y a peut être un problème ailleurs…

  6. Donc si j’ai bien compris, tu as 3 reverse proxy, et nginx est présent à 3 niveaux ?

  7. Non, le point d’entré est le port 443 qui est de suite redirigé sur 80 (varnish). Si la requête n’est pas en cache varnish alors elle est envoyée au serveur web sur le port 8080, qui va être ou pas traitée par HHVM.
    Comme je l’ai écris dans un commentaire, je me suis lancé ce défis de pourvoir utiliser varnish avec du ssl. Mais, avec du recul, ça me parait un peu usine à gaz.
    Cependant, varnish comme revers proxy cache est très puissant !
    Voilà, c’est faisable mais est-ce judicieux comme choix d’architecture ?
    A savoir, dû au problème de preview et de mise ne forme des lignes de code dans les articles, j’ai désactivé varnish (je vais continuer à bosser sur varnish sur mon 2ème VPS).

  8. Salut Fred,
    Non ta config est bien mais elle est plus pour du gros traffic. Generalement il est mieux d’utilise varnish sur une autre machine, bon apres quand on a un vps …

  9. Salut Gnuson,
    oui c’est une config pour du gros trafic mais là dans mon cas c’est un peu trop « to much » :-)
    et effectivement, l’idéal est de séparer sur 3 machines : revers proxy + serveur web + serveur BDD

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.