Auto-héberger son site internet

../../../../2013/10/29/auto-heberger-son-site-internet/
Antoine Van-Elstraete (antoine@van-elstraete.net)
mardi 29 octobre 2013





14 min.

Image d'illustration

Dans le cadre de l’auto-hébergement, voici un article qui expose la mise en place d’un serveur HTTP. Cela permet de créer un site web et de le rendre publique sur internet. Cet article fait parti de la série sur l’auto-hébergement.

Cet article décrit donc :

  • l’installation d’un serveur web (nginx) ;
  • la configuration du nom de domaine ;
  • l’installation d’un serveur PHP (php-fpm) ;
  • l’installation d’un serveur de bases de données (mariaDB) ;
  • l’installation et l’utilisation d’outils d’optimisation web (PageSpeed) ;
  • l’utilisation d’un content delivery network, “cdn” (CloudFront).

J’utilise ces outils sous Arch Linux, mais les explications suivantes sont assez génériques pour être appliquées à d’autres distributions à base de GNU/Linux.

Pré-requis

Un serveur (c’est à dire un ordinateur) capable de communiquer sur internet. Si cet ordinateur est relié au réseau via une « box » ou un routeur-modem, il faut rediriger les ports de communication web. Chaque port est associé à une application sur l’ordinateur, de cette façon les demandes qui arrivent sur un port donné sont correctement traitées par une application qui écoute sur ce port. Pour le web, les ports standards sont :

  • 80 pour le HTTP, c’est à dire les données de sites web diffusé en clair ;
  • 443 pour le HTTPS, c’est à dire les données de sites web diffusées de manière chiffrées.

Il faut rediriger ces ports vers l’ordinateur serveur, ainsi quand une requête arrive chez vous, elle arrive au point d’accès internet, et ce point d’accès est à même de renvoyer ces demandes vers la machine apte à les traiter.

Pour configurer cette redirection, c’est selon la notice d’utilisation du routeur.

N.B. : cette redirection n’a d’intérêt que pour les requêtes en IPv4. Si les requêtes arrivent en IPv6, elles arrivent directement sur la machine correspondant à l’adresse, il n’y a plus de « NATage ». Le but de cet article n’est pas d’expliquer IPv6, mais un bon début serait de lire cette page wikipédia. Ci-dessous, deux schémas résumant les situations.

Le serveur web

J’ai choisi d’utiliser nginx, mais il en existe d’autre, par exemple apache ou encore lighttpd. L’installation est très facile avec les distributions actuelles, grâce aux systèmes de paquets. Avec Arch Linux, il suffit d’installer le paquet nginx.

La configuration de nginx est assez simple. Personnellement, j’aime bien créer un fichier de configuration par section et utiliser ensuite des include dans le fichier principal (nginx.conf). Voici la partie principale du fichier de configuration :

user http;
worker_processes  4;

events {
    worker_connections  1024;
}

http {
    index   index.php index.htm index.html;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server_tokens off;
    gzip on;
    gzip_comp_level 4;
    gzip_proxied any;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_http_version 1.0;
include serveur_defaut.conf
}

Que cela signifie-t-il ?

  • Le serveur sera exécuté sous l’utilisateur « http ». C’est à cet utilisateur qui devront appartenir tous les fichiers à servir.
  • Quatre instance du serveur peuvent être lancées de manière simultanée. Il est recommandé dans la documentation à mettre ce nombre égal au nombre de processeurs disponibles.
  • Chaque processus peut gérer 1024 connections.
  • Diverses options permettant de choisir le nom des pages par défaut (index), de ne pas afficher la versions du serveur (« server tokens  »), et d’utiliser la compression gzip de niveau 4 sur certains types de fichiers.
  • Les options possibles dans cette partie sont définies sur http://wiki.nginx.org/CoreModule.

Ensuite, via « include serveur_defaut.conf » on va définir notre serveur virtuel (connu sous le nom de VirtualHost dans Apache). C’est cette partie qui définie quoi et comment servir.

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;
    server_name monserveur.tld;
    client_max_body_size    64m;
    client_body_buffer_size 16m;
    root    /srv/http/;
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

Dans ce fichier, on définit un serveur :

  • Qui écoute sur le port 80 en IPv4 et en IPv6 (reconnaissable au « [::] ») ;
  • Qui sera le serveur par défaut si aucune autre configuration de serveur virtuel ne peut être utilisée (« default_server ») ;
  • Qui répond quand on veut joindre http://monserveur.tld/ ;
  • Dont les tailles maximales pour une requête et du tampon, c’est à dire la taille maximale où une requête peu rester en mémoire et non être écrite dans un fichier temporaire sont définies à 64 Mo et 16 Mo ;
  • Dont la localisation des fichiers à servir est « /srv/http/ » ;
  • Dont on précise la liste et la localisation des pages d’erreurs ;
  • la possibilité d’utilisation des statistiques de connections (pour par exemple surveiller l’activité du serveur) avec « nginx_status ».

La liste des paramètres possibles et leurs explications sont sur la page http://wiki.nginx.org/HttpCoreModule.

Cette simple configuration suffit à servir un fichier HTML situé dans « /srv/http/ ».

Pour tester, c’est très simple. Il suffit de créer un fichier HTML nommé « index.html » et de pointer le navigateur vers l’adresse IP du serveur.

Mais pour accéder à un site web, on n’entre généralement pas directement l’adresse IP du site mais une adresse sous forme de texte, plus simple à retenir et plus expressive. Il s’agit du nom de domaine.

Le nom de domaine

Afin de convertir les adresses IP en adresse textuelles, on utilise le DNS. Les DNS (Domain Name System) sont en quelques sortes des annuaires de l’internet. Quand un client demande à se connecter à un domaine, il demande au serveur DNS a quelle adresse IP ce domaine correspond.

Pour obtenir un nom de domaine et y associer son ordinateur, il existe différentes solutions, certaines gratuites, mais la plupart payantes. En ce qui me concerne, j’ai choisis d’utiliser les service de Gandi.net. Une fois le nom de domaine acheté, il faut l’associer en créant un enregistrement DNS. Le service vendant le nom de domaine permet d’effectuer cette opération simplement via une interface sur leur site.

Une fois enregistré et associé, la relation entre nom de domaine et adresse IP est transmise à tous les serveur de nom de domaine. Selon les configurations, cette mise à jour peut prendre plusieurs heures (TTL).

Certains accès, notamment ADSL, n’ont pas une IP fixe. Cela signifie que de temps à autre l’adresse de l’accès internet est changée, sans que l’utilisateur en soit informé. Bien entendu, cela « casse » la résolution DNS. Pour éviter ces désagrements, il existe des solutions comme DynDNS. Mais avoir une vraie IP fixe reste la meilleur solution. Cette page donne de plus ample explication : https://wiki.auto-hebergement.fr/fournisseurs/fai.

Une fois le nom de domaine en poche, enregistré dans les serveur DNS, et renseigné dans la partie « server_name » de la configuration de nginx, l’hébergement web est actif.

Ça y est, le monde entier peut admiré les pages HTML fournit par votre serveur web à la maison. Mais le HTML, ce n’est que du statique, et pour mettre en place des outils comme un blog wordpress, un site internet dynamique, il est nécessaire d’utiliser une base de données et un serveur interprétant le PHP, langage de programmation dédié au web.

PHP-FPM

Nginx ne comprend pas le PHP. Lui, son truc, c’est simplement de servir et non d’interpréter. Sans outils pour gérer le PHP, il livrera un fichier contenant le code source PHP, sans aucun traitement. Pour gérer les pages contenant ce langage, j’ai choisis PHP-FPM. Il en existe d’autre, Apache pourrait également faire ce travail par exemple.

Un fois php-fpm installé avec votre gestionnaire de paquets préféré, il faut configurer nginx pour qu’ils communiquent ensemble. À cet effet, j’ai créer un fichier « php.conf », il me suffit ensuite d’ajouter un « include php.conf; » dans les paramètre du serveur virtuel qui sera amené à gérer du PHP. Voici ce fichier php.conf :

location ~ \.php {
    fastcgi_index    index.php;
    fastcgi_pass     unix:/run/php-fpm/php-fpm.sock;
    fastcgi_param    SCRIPT_NAME    $fastcgi_script_name;
    fastcgi_read_timeout 300;
    include         fastcgi.conf;
}

Ce fichier fait également appel à fastcgi.conf, qui permet une meilleur cohésion avec nginx :

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Plus d’informations sur ces fichiers : http://wiki.nginx.org/PHPFcgiExample.

Après ces modifications, nginx est capable de demander à php-fpm de compiler les fichiers PHP pour ensuite servir le résultats (donné sous forme HTML).

MariaDB

MariaDB est un *fork* (une copie se basant sur) de MySQL lancé par son fondateur suite au rachat de MySQL. C’est un gestionnaire de base de données. Bien entendu entièrement compatible avec MySQL, mais avec la garantie d’être libre. L’installation se fait comme tout autre programme de votre distribution Linux, avec parfois la subtilité d’utiliser le nom « mysql », pour des raisons de compatibilités. Pour la configuration, certaines distributions possèdent leur propre outil.

La communication avec PHP peut être établie avec 3 « connecteurs » différents, autant tous les activer car ce choix dépend des applications. Il faut ces trois lignes dans le fichier php.ini :

extension=mysqli.so
extension=mysql.so
extension=pdo_mysql.so

Le test

Pour vérifier que tout ce petit monde cohabite correctement, installer phpMyAdmin semble un bon test. Ce programme utilise une interface web (servie par nginx), programmée en PHP et permettant de gérer les base MySQL stockées par MariaDB.

Tout fonctionne ? C’est parfait, il est temps de booster votre serveur web.

Optimisations

Nginx est déjà un serveur web très rapide, de nombreux tests et comparatifs existent. Mais il est possible d’améliorer encore les choses, grâce au module « pagespeed », développé par Google. Ensuite, la bande passante disponible sur une ligne ADSL, surtout en envoi, n’est pas très large. Afin de distribuer des fichiers assez lourds (notamment des images, des photographies) il est possible d’utiliser un réseau de livraison de contenu (cdn).

PageSpeed

PageSpeed, dont la page officielle est https://developers.google.com/speed/pagespeed/module, permet d’optimiser le contenu afin de le rendre plus léger (gagner de la bande passante) et plus rapide (en terme de chargement final). De plus, c’est un logiciel libre. Pour l’installer avec nginx, il y a la méthode générale, mais certaines distributions propose une version de nginx embarquant pagespeed (sous Arch Linux : nginx-pagespeed). Une fois installé, il faut spécifier à nginx d’utiliser pagespeed. Pour cela, j’utilise un fichier pagespeed.conf que j’inclus dans la configuration des serveurs nginx (« include pagespeed.conf;  ») :

pagespeed on;
pagespeed FileCachePath /srv/http/ngx_pagespeed_cache;

pagespeed LowercaseHtmlNames on;
pagespeed EnableFilters convert_gif_to_png;
pagespeed EnableFilters extend_cache;
pagespeed EnableFilters extend_cache_pdfs;
pagespeed EnableFilters collapse_whitespace;
pagespeed EnableFilters rewrite_javascript;

# Évite l'ajout de headers
location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
  add_header "" "";
}
location ~ "^/ngx_pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all; }
location /ngx_pagespeed_message { allow 127.0.0.1; deny all; }

La première ligne active pagespeed, la seconde spécifie où les objets optimisés seront stockés avant d’être servis puis le bloc suivant active certains filtres de pagespeed. La liste des filtres disponibles est sur https://developers.google.com/speed/pagespeed/module/filters.

Pour aller encore plus loin, on peut par exemple stocker les objets optimisés dans un *ramdisk*, ce qui limite grandement les accès aux disques.

Content Delivery Network

Maintenant qu’un maximum d’objets ont été réduits, la bande passante requise est diminuée. Néanmoins, un site avec de nombreuses images, comme un album de photographies, reste lourd à transporter. En cas d’affluence, la connexion ADSL ne suffit pas et sature, entraînant des temps de chargement de plus en plus lents, pouvant parfois décourager certains visiteurs.

Grâce au content delivery network (cdn), ce type de problème est grandement réduit. En effet, l’objet n’est chargé qu’une seule fois depuis le serveur puis ce sont les serveurs du cdn qui livrent l’objet aux clients finaux.

Il existe différents service de cdn, comme Akamai, CloudFront, OVH cdn, … Pour ma part j’ai choisi CloudFront, d’Amazon. Je trouve ce service très intéressant car simple à utiliser, bien documenté, peu onéreux (seul le trafic généré est facturé) et scalable (c’est à dire qui s’adapte aux besoins). De plus, Amazon fournit un service de stockage (S3) qui peut servir de source pour le cdn. J’utilise donc deux types de cdn : un qui vient chercher les objets sur le serveur avant de le répliquer dans le cdn, et un autre qui va chercher les objets dans le panier S3 (ici, on s’éloigne du concept d’auto-hébergement). Ce deuxième principe est plutôt adapté pour un petit nombre de très gros objets (fichiers multimédia, archives).

CloudFront et nginx

Une fois la distribution CloudFront créée (avec comme source le serveur web auto-herbergé) et disponible, il suffit de configurer nginx pour qu’il redirige certaines demandes d’objets vers le cdn et non qu’il ne les délivre lui-même. Afin d’éviter de tomber dans une spirale infinie, si c’est le cdn qui demande l’objet (afin de pouvoir le redistribuer) il faut que nginx le délivre directement. Cette configuration s’obtient avec ce type de lignes :

location ^~ /Images {
        add_header Cache-Control "max-age = 31536000";
        if ($http_user_agent !~* (CloudFront)) {
            rewrite ^ http://cdn.monsiteweb.org$request_uri?;
        }
    }

Les objets situés dans le dossier « Images » sont redirigés vers « http://cdn.monsiteweb.org » si le demandeur n’est pas « CloudFront ». Au passage, on ajoute une date de péremption de l’objet de 31536000 secondes, soit environ 1 an. Cela signifie que cet objet restera dans le cdn 1 an avant d’être redemandé au serveur d’origine. Attention, cela signifie également que même si l’objet est modifié (en gardant le même nom), il ne sera pas rechargé et donc mis à jour dans le cdn avant une année.

Par exemple, http://monsiteweb.org/Images/chat.jpg devient http://cdn.monsiteweb.org/Images/chat.jpg.

Pour avoir un nom de domaine correspondant au cdn, il faut ajouter un enregistrement CNAME dans le DNS (voir avec votre fournisseur de nom de domaine la procédure).

PHP : xcache

Xcache permet de mettre en cache (en mémoire vive) le résultat de la compilation de code php. Ainsi, quand une autre demande de compilation est faite pour le même code source PHP, php-fpm ne le recompile pas et donne directement le résultat issu du cache. L’installation se fait logiquement avec le gestionnaire de paquet de la distribution puis dans php.ini (ici pour un cache de 128 Mo) :

extension=xcache.so
xcache.size=128M
xcache.var_size=128M

Conclusion

Monter un serveur web n’est peut être pas encore une opération grand public, mais s’auto-héberger est maintenant une réalité accessible. De plus certains outils viennent renforcer la capacité du serveur. Le seul point sensible reste la ligne vers internet, qui peut parfois être victime de panne et dans ce cas il faut faire confiance au prestataire xDSL ou fibre pour le rétablissement de la connexion. Une fois le serveur web en place, d’autres paramètres viennent s’ajouter : auto-héberger ses e-mails, auto-héberger ses documents… en d’autres termes, avoir son propre cloud sur internet.

Bonus : configuration nginx du site

Voici la configuration du serveur virtuel pour http://antoine.van-elstraete.net/ :

server {
    listen 80;
    listen [::]:80;
    server_name antoine.van-elstraete.net;
    root    /srv/http/wordpress_new/;
    keepalive_timeout  180;
    client_max_body_size 512M;
    access_log  /var/log/nginx/blog-antoine.access.log;
    error_log   /var/log/nginx/blog-antoine.errors.log;
    include php.conf;
    include pagespeed.conf;
    pagespeed Domain antoine.van-elstraete.net;
    pagespeed LoadFromFile "http://antoine.van-elstraete.net/wp-contents/uploads/"  "/srv/http/wordpress_new/wp-content/uploads/";
    pagespeed ImageMaxRewritesAtOnce 16;
    pagespeed ImageRecompressionQuality 95;
    pagespeed JpegRecompressionQuality 97;
    pagespeed EnableFilters convert_png_to_jpeg;
    pagespeed EnableFilters jpeg_subsampling;
    pagespeed EnableFilters recompress_images;
    pagespeed EnableFilters recompress_jpeg;
    pagespeed EnableFilters resize_images;
    pagespeed EnableFilters convert_jpeg_to_webp;
    pagespeed EnableFilters convert_to_webp_lossless;
    pagespeed EnableFilters inline_images;
    pagespeed EnableFilters remove_comments;
    location ~* {
        if ($http_user_agent ~* (Sogou)) {
            return 403;
        }
    }
    location ^~ /wp-content/uploads {
        add_header Cache-Control "max-age = 31536000";
        if ($http_user_agent !~* (CloudFront)) {
            rewrite ^ http://cdn.antoine.van-elstraete.net$request_uri?;
        }
    }

}

N.B. : Je bloque Sogou car il génère de trop nombreuses requêtes, et je n’ai pas d’audience en Chine.

pagespeed.conf :

pagespeed on;
pagespeed Statistics on;
pagespeed StatisticsLogging on;
pagespeed LogDir /var/log/pagespeed;

location /pagespeed_console {
    allow 127.0.0.1;
    allow ::1;
    allow IPV4/24;
    allow IPV6/64;
    deny all;
}

pagespeed FileCachePath /srv/http/ngx_pagespeed_cache;
pagespeed FileCacheSizeKb 512000;

pagespeed LowercaseHtmlNames on;
pagespeed EnableFilters convert_gif_to_png;
pagespeed EnableFilters convert_jpeg_to_progressive;
pagespeed EnableFilters extend_cache;
pagespeed EnableFilters extend_cache_pdfs;
pagespeed EnableFilters collapse_whitespace;
pagespeed EnableFilters rewrite_javascript;
pagespeed EnableFilters insert_dns_prefetch;
pagespeed EnableFilters move_css_to_head;
pagespeed EnableFilters combine_heads;
pagespeed EnableFilters convert_meta_tags;

location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
  add_header "" "";
}

location ~ "^/ngx_pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
location /ngx_pagespeed_statistics {
    allow 127.0.0.1;
    allow ::1;
    allow IPV4/24;
    allow IPV6/64;
    deny all;
}
location /ngx_pagespeed_message {
    allow 127.0.0.1;
    allow ::1;
    allow IPV4/24;
    allow IPV6/64;
    deny all;
}