Nginx : entrée en matière
Aujourd'hui, je vous propose de travailler sur la mise en service d'un serveur HTTP. Oui, mais pas forcément (que) Apache. Nginx.
Alors pourquoi Nginx ? Et bien cela remonte à a dernière session de Bargento où Emile Heitor avait fait une entrée en matière très patriotique, vantant ses vertues de Reverse proxy et de légèreté de fonctionnement par rapport à Apache. Jetons donc un coup d'oeil sur cette bête.
Comme je n'ai pas la prétention de tout connaître, je vais arquer le sujet sur plusieures parties : une première approche qui permettra d'aborder la bête de manière raisonnable, et sûrement plusieurs tips sur les configurations aux petits oignons.
Mais comme il ne faut jamais tout faire à la fois, commençons d'abord par...
L'installation
Quoique disponible via les dépôts, en version 0.8.17, je vais suivre les bonnes habitudes et l'installer depuis ses sources. Afin d'avoir la possibilité d'utiliser SSL, je recommande d'activer le support OpenSSL.$ wget http://nginx.org/download/nginx-0.8.53.tar.gz $ tar zxf nginx-0.8.53.tar.gz && cd nginx-0.8.53 $ ./configure --with-openssl=/usr/lib/openssl [...] Configuration summary + using system PCRE library + using OpenSSL library: /usr/lib/openssl + md5: using system crypto library + sha1 library is not used + using system zlib library nginx path prefix: "/usr/local/nginx" nginx binary file: "/usr/local/nginx/sbin/nginx" nginx configuration prefix: "/usr/local/nginx/conf" nginx configuration file: "/usr/local/nginx/conf/nginx.conf" nginx pid file: "/usr/local/nginx/logs/nginx.pid" nginx error log file: "/usr/local/nginx/logs/error.log" nginx http access log file: "/usr/local/nginx/logs/access.log" nginx http client request body temporary files: "client_body_temp" nginx http proxy temporary files: "proxy_temp" nginx http fastcgi temporary files: "fastcgi_temp" nginx http uwsgi temporary files: "uwsgi_temp" nginx http scgi temporary files: "scgi_temp" $ make && make install [...] $ ll /usr/local/nginx/ total 24 drwxr-xr-x 6 root root 4096 2010-10-29 16:12 . drwxr-xr-x 27 root root 4096 2010-10-29 16:12 .. drwxr-xr-x 2 root root 4096 2010-10-29 16:12 conf drwxr-xr-x 2 root root 4096 2010-10-29 16:12 html drwxr-xr-x 2 root root 4096 2010-10-29 16:12 logs drwxr-xr-x 2 root root 4096 2010-10-29 16:12 sbinFichiers de configuration, html, binaires et logs, tout est écrit dans /usr/local/nginx/, contrairement à une installation via les dépôts qui m'a semé des fichiers partout, à commencer par le /etc/nginx/.
Les jeux de configuration
Telle quelle, la configuration est viable et le lancement de nginx via /usr/local/sbin/nginx se fait de suite. On peut alors consulter le message de bienvenue sur http://127.0.0.1. Voici le fichier de configuration, tel qu'il est juste après installation.#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # Error page redirection on static error pages /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # Pass PHP scrips to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # Pass PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443; # server_name localhost; # ssl on; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_timeout 5m; # ssl_protocols SSLv2 SSLv3 TLSv1; # ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }On peut voir qu'il est composé de blocs permettant de définir des paramètres au sein d'un périmètre. 3 grands blocs se détachent à prime abord :
- Le bloc général, contenant les paramètres propres au daemon nginx (utilisateur faisant tourner le daemon, logs, pid, ...)
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid;
- Un bloc events, qui gère les paramètres concernant les connexions, et dont on ne s'occupera pas du tout pour le moment
events { worker_connections 1024; }
- Un bloc regroupant les paramètres propres aux flux http. C'est sur ce bloc que vous allez majoritairement travailler, puisqu'il contient notamment des sous blocs servers.
http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirection des pages d'erreur sur les pages statiques /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
Le fichier de configuration est donc établi comme une hiérarchie de directives, qui seront parcourues séquentiellement.
Maintenant qu'on sait à quoi s'en tenir, jouons un peu !
Un exemple de serveur HTTP
La spécification des servers NGinx est très semblable aux VirtualHost d'Apache. Nginx peut parfois donner une impression de plus fine granularité, notamment grâce aux jeux des locations, et la possibilité de créer un log par serveur de manière intuitive. Nous allons nous familiariser avec une configuration classique, qui est donnée par le wiki. Pour plus de lisibilité, je ne reprendrais pas le bloc général, qui ne change pas, mais m'attacherais plus aux sous-blocs server. Par contre, de votre côté, et pour que cela fonctionne, il faut bien évidemment que le fichier soit complet !server { listen 80; server_name *.domain.com; rewrite ^ http://www.domain.com$request_uri permanent; } server { listen 80; server_name www.domain.com; index index.html; root /home/domain.com }Ici, le premier bloc intercepte toutes les requêtes interrogeant n'importe quel domaine de domain.com sur le port 80. Les requêtes interceptées seront réécrites telles qu'elles interrogeront désormais www.domain.com, toujours sur le même port.
De l'utilité des server_name
Cette configuration est intuitive pour nous, mais elle souligne un point important : pourquoi www.domain.com n'est-il pas compris dans *.domain.com ? Car * signifie en gros tout caractère ou chaîne de caractère. Et bien parce que NGinx est intelligent et validera l'expression la plus spécifique. En réalité, Nginx va s'appuyer sur le Host Header spécifié par le client. Lorsque celui n'est pas renseigné (IP directement saisie, établissement d'une session SSL, ...), il est possible d'orienter tout de même la requête en spécifiant une valeur spéciale en tant que server_name : _En fait, NGinx évalue et dirige les requête dans un ordre bien particulier :
- De manière prioritaire, les noms statiques, complets
- En second, les noms commençant par une * comme notre *.domain.com
- Ensuite, ceux finissant par une * -logiquement, www.domain.*
- Enfin, les noms modulés par des expressions régulières.
rewrite
Rewrite fait partie des instructions NGinx. Comme son nom l'indique, elle réécrit les URL des requêtes, et donc les redirige vers le bloc server adéquat. Elle prend en paramètre une expression régulière, le remplacement voulu et un flag. Attention cependant, la regex ne travaille que sur le chemin relatif, donc notre URI. Elle ne prend donc pas en compte le serveur spécifié dans la requête à réécrire. Le flag, quant à lui, permet de contrôler la fin des processus de réécriture.- last : qui effectue la réécriture, après quoi il recherche l'URI et la location correspondante
- break : qui ne fait que la réécriture
- redirect : renvoie une redirection temporaire (code 302)
- permanent : renvoie une redirection permanente (code 301)
$request_uri
Vous avez sûrement remarqué le très compréhensible $request_uri. Mais mais mais cette variable n'apparaît nulle part ailleurs dans la configuration ! Comment NGinx peut savoir à quoi cela correspond ? NGinx est comme ça. Il met à disposition des variables qui, pour la plupart ne sont pas modifiables, afin de pouvoir être facilement explicite avec ce que l'on veut faire sans pour autant partir from scratch. Pour faire simple, $request_uri correspond à la seconde partie de l'URL originalement requêtée, celle qui commence juste après l'adresse du serveur et qui inclut les arguments. S'il faut un exemple : pour l'URL http://www.domain.com/support/docs/index.php, $request_uri vaudrait /support/docs/index.php. Il est d'ailleurs spécifié dans le wiki que $request_uri ne concerne pas les URI qui ont fait l'objet de réécritures. Ces URI-là seront disponibles via la variable $uri, qui contient l'URI actuellement utilisée. Pour tout savoir, par ici.Location, substitution et regex sous NGinx
Une regex -une expression régulière si vous préférez-, est un pattern de recherche qui identifiera ou non un ou plusieurs éléments. La regex est tout un art, mais je suppose que beaucoup d'entre vous le connaissent et le manient déjà. Sous NGinx aussi, on a la possibilité d'évaluer des patterns assez complets et, en fonction de leur identification, leur associer des traitements. Les regex peuvent être évaluées partout, y compris sur les location blocs. Lorsque c'est le cas, Nginx adoptera le même comportement que lors de l'évaluation des Host Header et ira chercher la correspondance la plus exacte entre l'URI et les location blocs. Bien entendu, il peut y avoir plusieurs location blocs par server bloc. Par exemple, ici, nous avons un location bloc exprès pour le traitement des fichiers .php.server { listen 80 default; server_name _; index index.html; root /var/www/default # Toute URI commençant par /forum matchera le bloc et passera par un rewrite location /forum { # Capture de l'URI et redirection de celle-ci sur le sous-domaine correspondant. rewrite forum(.*) http://forum.domain.com$1 permanent; } location ~* \.php$ { # exception IF : if doit toujours être évité, notamment parce que NGinx n'évalue pas en terme de fichier mais en terme de localisation. # L'exception est là pour combler un trou de sécurité : # Par exemple, empêcher l'execution de /forum/avatars/user2.jpg sur une requête d'URI /forum/avatars/user2.jpg/index.php # Une autre solution consiste de manière plus propre à mettre cgi.fix_pathinfo=0 dans le php.ini # Référence : http://wiki.nginx.org/Pitfalls#Passing_Every_.7E_.5C.php.24_request_to_to_PHP if (!-f $request_filename) { return 404; } fastcgi_pass 127.0.0.1:9000; } } server { server_name forum.domain.com; listen 80; index index.php; root /home/domain.com/forum; }
Cet exemple est également intéressant car il nous montre comment réutiliser un pattern détecté : il suffit d'entourer le pattern de parenthèse et d'y faire référence avec un $1 ! Cela permet d'effectuer des réécritures au niveau des URI demandées, et parfois même de les envoyer sur, au hasard, une adresse en https...
En parlant de HTTPS...
Puisque nous avons activé le support SSL, autant s'en servir ! Avant toute chose, mieux vaut avoir son set certificat SSL / clé prêt. Si vous avez besoin d'un petit coup de pouce, jetez un coup d'oeil par ici ! Voici une petite configuration assez simple et qui est facilement identifiable en tant que configuration HTTPS.server { access_log /var/log/nginx/access.log main; add_header Cache-Control public; error_log /var/log/nginx/error.log info; expires 90d; index index.html; listen 127.0.0.1:443; root /var/www/htdocs; server_name domain.com www.domain.com; ## SSL Certs ssl on; ssl_certificate /ssl_keys/domain.com.crt; ssl_certificate_key /ssl_keys/domain.com.key; ssl_ciphers HIGH:!ADH:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ## Forcer HTTPS add_header Strict-Transport-Security "max-age=2592000; includeSubdomains"; ## Toute autre erreur renvoie une page d'erreur générique error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 500 501 502 503 504 505 /example_error_page.html; location /example_error_page.html { internal; } } }Ce qui change, par rapport à notre serveur HTTP, c'est le port sur lequel écoute le serveur et cette section SSL. Dans cette section, le SSL est explicitement activé et tous les éléments relatifs aux certificats et à l'encryptage sont stipulés. Et si l'on veut ne passer que par ce serveur, il ne reste plus qu'à réécrire les requêtes adressées à notre serveur HTTP simple et botter en touche sur la sollicitation d'autres server_name.
# HTTP redirigé sur HTTPS server { add_header Cache-Control public; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log info; expires 90d; listen 127.0.0.1:80; root /var/empty; server_name domain.com www.domain.com; location / { if ($host ~* ^(domain\.com|www\.domain\.com)$ ) { rewrite ^/(.*)$ https://domain.com/$1 permanent; } return 444; } } # default server server { server_name _; #default return 444; }
Reverse proxy ?
C'est bien beau tout ça, mais avant d'être un serveur HTTP, NGinx est avant tout un Reverse Proxy. Un reverse proxy est, comme son nom l'indique, un serveur proxy, mais qui fonctionne en inverse d'un proxy habituel. En effet, un server proxy permet aux utilisateurs interne à un réseau d'accéder à des services externes à ce réseau. Et bien un reverse proxy, c'est un serveur qui permet à des entités externes au réseau d'accéder aux services de ce réseau. Cela implique que les reverse proxy doivent être mis devant les serveurs ciblés, typiquement une ferme de serveurs Web, afin de pouvoir router correctement les flux. Cela permet également de gérer un minimum de sécurité et de dispatcher la charge sur différents serveurs en cas de besoin. Un petit exemple de reverse proxying :http { [...] ## Proxy options proxy_buffering on; proxy_cache_min_uses 3; proxy_cache_path /usr/local/nginx/proxy_temp/ levels=1:2 keys_zone=cache:10m inactive=10m max_size=1000M; proxy_cache_valid any 10m; proxy_ignore_client_abort off; proxy_intercept_errors on; proxy_next_upstream error timeout invalid_header; proxy_redirect off; proxy_set_header X-Forwarded-For $remote_addr; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_read_timeout 60; ## Backend serveurs (webserver1 est le serveur primaire et webserver2 son backup dans le cas où webserver1 est down) upstream webbackend { server webserver1.domain.lan weight=10 max_fails=3 fail_timeout=30s; server webserver2.domain.lan weight=1 backup; } server { add_header Cache-Control public; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; index index.html; limit_conn gulag 50; listen 127.0.0.1:80 default; root /usr/local/nginx/html; server_name _; ## On ne traite que les requêtes s'adressant à domain.com ## les autres sont bottées en 444 if ($host !~ ^(domain.com|www.domain.com)$ ) { return 444; } ## Seuls les chemin complets depuis la racine sont traités. ## Si on veut juste référencer les noms de fichiers, utiliser $request_filename au lieu de $request_uri location / { if ($request_uri ~* (^\/|\.html|\.jpg|\.pl|\.png|\.css|\.ico|robots\.txt)$ ) { break; } return 444; } ## PROXY - Forum location /forum/ { proxy_pass http://forum.domain.lan/forum/; } ## PROXY - Data location /files/ { proxy_pass http://data.domain.lan/; } ## PROXY - Web location / { proxy_pass http://webbackend; proxy_cache cache; proxy_cache_valid 200 24h; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; proxy_ignore_headers Expires Cache-Control; } ## Toute autre erreur renvoie la page d'erreur générique error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 500 501 502 503 504 505 506 507 /error_page.html; location /error_page.html { internal; } } }On voit bien dans cet exemple que le reverse proxying s'arque sur 2 points : les paramétrages généraux, qui s'appliqueront à toutes les requêtes, et les proxy_pass, qui aiguilleront les URI matchant une location en particulier sur des serveurs en backend. Pour les curieux, jetez un coup d'oeil ici pour avoir la liste des options et leurs rôles.