Créer son site web avec Nginx et PHP-FPM sous Debian

Le but de ce tutoriel est de pouvoir héberger ses sites web sur son propre serveur.

  • Unix (Debian) comme système d’exploitation pour le serveur
  • Nginx comme serveur HTTP
  • PHP-FPM pour générer des pages HTTP coté serveur à partir de script PHP.

Le choix du matériel pour le serveur est libre. Il faut toutefois qu’il réponde aux contraintes / prérequis des différents module à installer.

Installer Nginx 1.9.10 avec PHP7-fpm sur Debian 8 (Jesie)

Installer Nginx depuis jessie-backports

Installer le serveur web Nginx depuis le dépôt officiel sous Debian

:$ apt-get update && apt-get -t jessie-backports install nginx

:$ apt-cache policy nginx
nginx:
  Installé : 1.9.10-1~bpo8+4
  Candidat : 1.9.10-1~bpo8+4
 Table de version :
 *** 1.9.10-1~bpo8+4 0
        100 http://ftp.fr.debian.org/debian/ jessie-backports/main amd64 Packages
        100 /var/lib/dpkg/status
     1.6.2-5+deb8u4 0
        500 http://ftp.fr.debian.org/debian/ jessie/main amd64 Packages
        500 http://security.debian.org/ jessie/updates/main amd64 Packages

Vérifier la version de Nginx

:$ nginx -v
nginx version: nginx/1.9.10

Retrouver le nombre de core du système

:$ nproc
2

Retrouver la limite système du nombre de fichier ouvert par processus

:$ ulimit -n
65536

Ajuster le nombre de Nginx worker_processes en fonction des caractéristique matèriel du sever (une pratique commune est de prendre 1 worker process par core).

Ajuster le nombre de connection simultannée que Nginx accèpte par worker process.

:$ nano /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
        worker_connections 768;
        # multi_accept on;
}

events {
    worker_connections  768;
}

#worker_rlimit_nofile    1024;  # 2 *  worker_connections

Installer le module Nginx dans Webmin (optionnel)

Depuis Webmin -> Webmin Configuration -> Webmin Modules

L’installation avec « Third party module from » et en filtrant sur Nginx n’a pas fonctionnée.

J’ai télécharger le fichier depuis Internet sur mon pc et utilisé l’option « From uploaded file »

Sur le site Webmin Télécharger le module Nginx : http://www.webmin.com/third.html

Le module Nginx webserver est installé dans /usr/share/webmin/nginx (92 kB) sous la catégorie Servers.

Recharger la page Wedmin pour que Nginx webserver soit visible dans la catégorie Servers de Webmin :Webmin: Nginx Webserver

Installer php7.1-fpm depuis le dépôt de Ondřej Surý

Vérifier si php5 est installé et le desinstaller le cas échéant

:$ apt-cache policy php5 php5-fpm
php5:
  Installé : (aucun)
  Candidat : 5.6.30+dfsg-0+deb8u1
 Table de version :
     5.6.30+dfsg-0+deb8u1 0
        500 http://security.debian.org/ jessie/updates/main amd64 Packages
     5.6.29+dfsg-0+deb8u1 0
        500 http://ftp.fr.debian.org/debian/ jessie/main amd64 Packages
php5-fpm:
  Installé : (aucun)
  Candidat : 5.6.30+dfsg-0+deb8u1
 Table de version :
     5.6.30+dfsg-0+deb8u1 0
        500 http://security.debian.org/ jessie/updates/main amd64 Packages
     5.6.29+dfsg-0+deb8u1 0
        500 http://ftp.fr.debian.org/debian/ jessie/main amd64 Packages

Debian 8 ne propose pas la version 7.1 de php-fpm.

Ajouter le dépôt de Ondřej Surý au gestionaire de paquets. Il est l’administrateur officiel de Debian pour les paquets PHP principaux (core) et d’atutres modules PHP.

:$ apt-get update && apt-get install apt-transport-https lsb-release ca-certificates
:$ wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
:$ echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
:$ apt-get update

Vérifier la version de php7.1-fpm

:$ apt-cache policy php7.1-fpm
php7.1-fpm:
  Installé : (aucun)
  Candidat : 7.1.2-1+0~20170217124815.15+jessie~1.gbp510ca7
 Table de version :
     7.1.2-1+0~20170217124815.15+jessie~1.gbp510ca7 0
        500 https://packages.sury.org/php/ jessie/main amd64 Packages

Installler PHP 7.0 pour Nginx et quelques modules utiles pour Nextcloud :

:$ apt-get install php7.1-fpm php7.1-xml php7.1-zip php7.1-bz2 php7.1-mbstring php7.1-imap php7.1-gd php7.1-curl php7.1-cli php7.1-intl php7.1-mcrypt php7.1-imagick php7.1-ldap php7.1-mysqlnd

Vérifier la version de PHP :

:$ php -v
PHP 7.1.2-1+0~20170217124815.15+jessie~1.gbp510ca7 (cli) (built: Feb 17 2017 13:15:28) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.1.2-1+0~20170217124815.15+jessie~1.gbp510ca7, Copyright (c) 1999-2017, by Zend Technologies

la version retourné est celle utilisé par les ligne de commande CLI pour connaitre la version utilisé par le site web il est préférable d’utiliser phpinfo()

Une fois les différents modules installés, il est important d’ajuster la configuration de PHP-FPM à Nginx et MariaDB.


Références :

installer-php-7-debian-8-jessie-depot-dotdeb

deb.sury.org

packages.sury.org

installing-php-7-1

Configurer PHP-FPM pour fonctionner avec Nginx et MariaDB

Dans ce tutoriel, PHP-FPM doit être déjà installé sur le serveur. Ajuster les lignes de commandes en fonction de la version de PHP-FPM installée sur votre serveur.

$: service --status-all | grep -i fpm
 [ + ]  php7.1-fpm

Pour savoir où est le fichier php.ini utilisé par les commandes CLI

:$ php -i | grep 'php.ini'
Configuration File (php.ini) Path => /etc/php/7.1/cli
Loaded Configuration File => /etc/php/7.1/cli/php.ini

Pour retrouver le fichier php.ini utilisé par le serveur web, une solution est d’afficher le résultat de la fonction PHP <?php phpinfo();?> dans une page Web. Sur mon serveur le fichier est /etc/php/7.1/fpm/php.ini

Configurer PHP-FPM

La litérature du web laisse entendre que l’utilisation de socket unix plutôt que TCP améliore les perfomances avec PHP. Je n’ai pas vérifier ce point mais j’ai pris le parti pour l’instant d’utiliser les socket unix. Une socket unix est un « fichier » qui va remplacer la communication la stack protolaire TCP.

Attention, il faut les définir le fichier « socket » sur un disque mémoire et pas sur un disque physique au risque de voir les performance et la durée de vie du disque juter drastiquement (ex: /run sous debian qui est monté par défaut sur un disque mémoire).

Configurer les informations suivantes dans le fichier de conifguration de PHP-FPM (faire une copie du fichier avant de le modifier)

:$ cp /etc/php/7.1/fpm/pool.d/www.conf /etc/php/7.1/fpm/pool.d/www.conf.bak
:$ nano /etc/php/7.1/fpm/pool.d/www.conf
user = www-data
group = www-data
; listen variable hast to be used in the nginx server configuration file to set the fastcgi_pass variable
listen = /run/php/php7.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; to Redirect worker stdout and stderr into main error log.
catch_workers_output = yes
; to configure environement variables
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

Pour connaitre les informations à configurer dans la variable d’environement PATH, la ligne de commande suivante peut être utilisée :

:$ printenv PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Redémarrer php-fpm pour prendre en compte les modifications

:$ /etc/init.d/php7.1-fpm restart
[ ok ] Restarting php7.1-fpm (via systemctl): php7.1-fpm.service.

Pour accéder au fichier socket, il faut que les droits soit donnés en lecture et écriture à l’utilisateur wwww-data. Vous pouvez vérifier les droits avec la commande suivante et les changer avec chown le cas échéant.

$: ls -l /run/php/php7.1-fpm.sock
srw-rw---- 1 www-data www-data 0 nov.   9 12:42 /run/php/php7.1-fpm.sock

Ajouter la configuration PHP-FPM dans Nginx

Créer le répertoire snippets s’il n’existe pas.

$: mkdir /etc/nginx/snippets

Créer un fichier avec l’extrait de configuration qu’il faudra ajouter à la configuration de votre serveur dans Nginx.

$: nano /etc/nginx/snippets/fastcgi-php.conf
# regex to split $uri to $fastcgi_script_name and $fastcgi_path
fastcgi_split_path_info ^(.+.php)(/.+)$;

# Check that the PHP script exists before passing it
try_files $fastcgi_script_name =404;

# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";

# Bypass the fact that try_files resets $fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;

fastcgi_index index.php;
include fastcgi.conf;

L’extrait inclus lui même un autre fichier de configuration pour les paramètres FastCGI.

$: nano /etc/nginx/fastcgi-php.conf
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  REQUEST_SCHEME     $scheme;
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;

Configurer PHP pour fonctioner avec MariaDB en local

:$ cat /etc/mysql/my.cnf | grep -i socket
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
socket        = /var/run/mysqld/mysqld.sock
socket        = /var/run/mysqld/mysqld.sock
socket        = /var/run/mysqld/mysqld.sock

:$ ls -l /var/run/mysqld/mysqld.sock
srwxrwxrwx 1 mysql mysql 0 dc.  26 19:30 /var/run/mysqld/mysqld.sock

:$ cp /etc/php/7.1/fpm/php.ini /etc/php/7.1/fpm/php.ini.bak

:$ nano /etc/php/7.1/fpm/php.ini
[Pdo_mysql]
pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock

[MySQLi]
mysqli.default_socket = /var/run/mysqld/mysqld.sock

Redémarrer php-fpm pour prendre en compte les modifications

:$ /etc/init.d/php7.1-fpm restart
[ ok ] Restarting php7.1-fpm (via systemctl): php7.1-fpm.service.

Ajuster la gestion des processus de PHP-FPM en fonction de votre système

L’article PHP-FPM – process management explique bien comment estimer les resources nécessaires à PHP-FPM pour fonctionner de façon optimale.

Si PHP-FPM n’est pas optimisé pour votre système, PHP-FPM peut ne pas être capable de répondre aux demandes en retournant une erreur, ou pire en cas de manque de mémoire.

Un des paramètres important est le nombre de processus fils que PHP-FPM est autorisé à executer simultanément (pm.max_children). Ce nombre ne doit pas être trop gros pour éviter de consomer l’ensemble des resources du serveur mais il ne doit pas être trop petit non plus.

Si le nombre de processsus fils est trop petit, PHP-FPM capture l’erreur suivante dans le fichier de log du serveur : server reached pm.max_children setting (5), consider raising it. Où 5 est la valeur actuelle du paramètre.

Les raisons peuvent être les suivantes :

  • Trop de requètes utilisateur simultanées à traiter
  • Temps d’execution des scripts lent (manque de resource sur le serveur ou bug dans le script)

La configuration de la gestion des processus de PHP-FPM est dans le fichier www.conf.

:$ nano /etc/php/7.1/fpm/pool.d/www.conf
; Note: - It will only work if the FPM master process is launched as root
;   static  - a fixed number (pm.max_children) of child processes;
;             pm.max_children      - the maximum number of children that can
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;             pm.max_spare_servers - the maximum number of children in 'idle'
;             pm.max_children           - the maximum number of children that
;             pm.process_idle_timeout   - The number of seconds after which
pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
pm.max_children = 5
; Note: Used only when pm is set to 'dynamic'
pm.start_servers = 2
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 1
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 3

Estimer le nombre maximal de processus fils pour PHP-FPM

Pour estimer le nombre de processus fils, il faut prendre en compte les resources globales du serveur ainsi que la mémoire pouvant être allouée à chaque processus enfant.

[nombre de processus possible à executer] = [Mémoire totale disponible pour PHP] / [Mémoire par processus fils]

Si votre seveur est dédié aux application PHP, il est possible de réserver uniquement de la mémoire pour le système d’exploitation et allouer le rester à l’utilisation de PHP. Par exemple, avec 8 GO de mémoire, garder 512 MO pour le système, et utiliser le reste pour PHP (7,5 GO)

Sur mon serveur, les applications PHP doivent partager les resources avec d’autre type d’application. J’ai opté pour n’utiliser que la moitier de la mémoire disponible : Mémoire totale disponible pour PHP = 4000.

Pour estimer la mémoire par processus fils, il est possible de prendre la valeur mémoire maximum (memory_limit) défini dans la configuration de PHP (php.ini) pour l’execution d’un script PHP.

$: cat /etc/php/7.1/fpm/php.ini | grep -i memory_limit
memory_limit = 128M

Une autre approche est d’estimer la valeur moyenne consomée par les scripts PHP tournant sur votre serveur.

ps --no-headers -o "rss,cmd" -C php-fpm7.1
54340 php-fpm: master process (/etc/php/7.1/fpm/php-fpm.conf)
102780 php-fpm: pool www
87040 php-fpm: pool www
59676 php-fpm: pool www

Pour avoir la moyenne dans un format lisible

$: ps --no-headers -o "rss,cmd" -C php-fpm7.1 | awk '{ sum+=$1 } END { printf ("%d%sn", sum/NR/1024,"Mb") }'
74Mb

Basé sur la formule plus haut, le nombre de processus possible à excuter simultanément peut être calculé à partir de la mémoire moyenne necessaire au fonctionnement des scripts PHP

__pm.max_children = 4000 MO / 74 MO = 54__

Estimer les autres paramètres en fonction du nombre maximal de processus enfant pour PHP-FPM

Le nombre minimum de processus au repos (idle) voulus peut être fixé à 20% du nombre maximum de processus fils

pm.min_spare_servers = 54 x 20 / 100 = 11

Le nombre minimum de processus au repos (idle) voulus peut être fixé à 60% du nombre maximum de processus fils

pm.max_spare_servers = 54 x 60 / 100 = 32

Le nombre de processus fils à créer au démarrage est calculé à partir de la formule par défaut et documentée dans la description du paramètre : Valeur par défaut: min_spare_servers + (max_spare_servers - min_spare_servers) / 2

__pm.start_servers = 11 + (32 – 11) / 2 = 21__

Redémarrer php-fpm pour prendre en compte les modifications :

:$ systemctl restart php7.1-fpm.service

Références :

PHP-FPM – process management

PHP Configuration

PHP FastCGI Example

NGINX WebSocket Performance

Configurer un site web dans Nginx

Pour ce tutoriel, Nginx et PHP-FPM doivent être installé et PHP-FPM doit être configuré/ajusté pour fonctioner avec Nginx.

Créer les répertoires du site web

Les fichiers du site web sont stoqués sur un disque différent de celui qui héberge le système d’exploitation (Debian). Si ce n’est pas le cas pour vous, vous pouvez créer le répertoire /var/mon.domain.fr/www ou /var/www. Le nomage est libre. Il faut toutefois penser que ce répertoire devra pouvoir être accessible à l’utilisateur www-data utilisé par le serveur web.

Le répertoire /mnt/data/websites/mon.domain.fr/www va contenir les fichiers du site web.

:$ mkdir -p /mnt/data/websites/mon.domain.fr/www

Il est possible de créer un point de montage à la racine, afin de pouvoir accéder aux fichiers plus facilement avec : /website

:$ mkdir /websites
:$ mount -R /mnt/data/websites /websites
:$ nano /etc/fstab
/mnt/data/websites  /websites   none    bind    0       0

Créer le fichier index.php à la racine du répertoire de votre site web. La ligne de commande suivante ajoute un code PHP au fichier afin d’afficher les informations de PHP depuis le navigateur web.

:$ printf "n<?php var_export($_SERVER)?>n<?php phpinfo(); ?>n" > /websites/[hostname]/www/index.php

Il est important de modifier le contenu de ce fichier lorsque le site sera mis en production. Les informations affichées par la commande PHP sont utiles mais elles peuvent être aussi exploitées à votre insu.

Donner les droits au serveur web d’accéder aux fichiers du site en lecture et écriture. www-data:www-data peut être remplacé par root:www-data pour limiter les droits en lecture seule. Toutefois, si une partie de votre site nécessite d’écrire des données sur le disque, par exemple pour sauvegarder une configuration, il faudra changer les droits des fichiers concernés pour redonner les droits en écriture à l’utilisateur www-data.

:$ chown -R www-data:www-data /websites/mon.domain.fr

Configurer et activer le site web dans Nginx

Désactiver le site web par défaut configuré dans Nginx

:$ rm /etc/nginx/sites-enabled/default

Créer la configuration de votre site web dans Nginx. Remplacer mon.domain.fr avec le nom de votre domaine et /websites/mon.domain.fr/www avec le chemin du répertoire racine contenant les fichiers de votre site web.

:$ cp /etc/nginx/sites-available/default /etc/nginx/sites-available/http_mon.domain.fr
:$ nano /etc/nginx/sites-available/http_mon.domain.fr
# Excludes requests with HTTP status codes 2xx (success) and 3xx (redirection)
map $status $loggable {
    ~^[23]  0;
    default 1;
}
upstream php-handler {
    server unix:/run/php/php7.1-fpm.sock;
}

# Default server configuration
#
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /websites/mon.domain.fr/www;

    # Add index.php to the list if you are using PHP
    index index.php index.html index.htm;

    server_name mon.domain.fr local.mon.domain.fr;

    # Excludes requests with HTTP status codes 2xx (success) and 3xx (redirection)
    # The predefined 'combined' format will be used
    #access_log  /var/log/nginx/mon.domain.fr.access.log combined if=$loggable;
    #error_log  /var/log/nginx/mon.domain.fr.error.log warn;

    # Add headers to serve security related headers
    # Before enabling HTTP Strict-Transport-Security headers please read into this
    # topic first.
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    #add_header Strict-Transport-Security "max-age=15768000;";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
    # From Nextcloud 14
    add_header Referrer-Policy no-referrer;

    # Remove X-Powered-By, which is an information leak
    fastcgi_hide_header X-Powered-By;

    location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
        }

    location = /favicon.ico {
            log_not_found off;
            access_log off;
    }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    # Connecting NGINX to PHP FPM (default) FastCGI server listening php-handler
    # https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/
    location ~ [^/].php(/|$) {
        include snippets/fastcgi-php.conf;
        fastcgi_pass php-handler;
    }

}

Le site web est configuré mais il doit être ajouté à la liste des site actifs pour que Nginx prenne en compte la configuration.

:$ ln -s /etc/nginx/sites-available/http_[hostname]e /etc/nginx/sites-enabled/http_mon.domain.fr

Tester la configuration et la corriger en cas d’érreure

:$ nginx -t

Pour que la nouvelle configuration soit prise en compte, il faut redémarrer le serveur Nginx ou la recharger.

Redémarrer le Nginx

:$ /etc/init.d/nginx restart
[ ok ] Restarting nginx (via systemctl): nginx.service.

Recharger la configuraiton dans Nginx

:$ nginx -s reload

Configurer le nom de domaine local et tester l’installation de Nginx

L’adresse local.mon.domain.fr est utilisée sur le réseau local alors que mon.domain.fr est l’adresse publique du site.

L’adresse publique doit être configurée dans un DNS pour que les ordinateurs distants puissent accéder au site web.

L’adresse locale doit être configurée dans le fichier hosts des ordinateurs du réseau local (pas sur le serveur Debian).

:$ sudo nano /etc/hosts
192.168.1.22 local.mon.domain.fr

Accéder au site web depuis l’URL local : http://local.mon.domain.fr/

PHP Info

Laisser un commentaire