8

Installation d’un serveur web LNMPV sur un VPS avec Debian

Cet article est avant tout un mémo pour mes besoins sur l’installation et la configuration d’un serveur web complet pour héberger des blogs/sites WordPress et autres.

Mon serveur web est construit sur :

  • LNMPV : Linux Nginx MariaDB Php-fpm Varnish

Tout ce petit monde est orchestré par la distribution GNU/Linux Debian 8 Jessie.

LNMPV-debian-memolinux

Préparation du VPS

Dans le cas de mon VPS, j’ai commencé par mettre à l’heure le système, désinstallé Apache2 puis installé l’éditeur nano et htop.

nb : toutes les commandes écrites dans l’article sont exécutées en tant que l’utilisateur : root.

    • Mettre à l’heure le système :
    dpkg-reconfigure tzdata
      • Désinstaller Apache2 :
      apt autoremove --purge apache2
        • Installer l’éditeur nano et htop :
        apt install nano htop

        Nginx

          • Ajout du dépôt officiel pour bénéficier de la dernière version stable :
          echo "deb http://nginx.org/packages/debian/ jessie nginx" >> /etc/apt/sources.list.d/nginx.list
          wget http://nginx.org/keys/nginx_signing.key
          apt-key add nginx_signing.key
          
            • Installation de Nginx :
            apt update && apt install nginx
              • Mon fichier nginx.conf :
              user www-data;
              worker_processes  auto;
              worker_rlimit_nofile 8192;
              events {
                      # max_clients = worker_processes * worker_connections
                      worker_connections  1024;
                      # Only for Linux 2.6 or >
                      use epoll;
                      # Accept as many connections as possible
                      multi_accept on;
              }
              http {
                      # Mime types
                      include         mime.types;
                      default_type    application/octet-stream;
                      # Log format
                      set_real_ip_from        127.0.0.1;
                      real_ip_header          X-Forwarded-For;
                      log_format      main '$remote_addr - $remote_user [$time_local]  $status '
                              '"$request" $body_bytes_sent "$http_referer" '
                              '"$http_user_agent" "$http_x_forwarded_for"';
                      # Some tweeks...
                      sendfile                        on;
                      tcp_nodelay                     on;
                      # Timeouts
                      keepalive_timeout               65;
                      client_body_timeout             30;
                      client_header_timeout           30;
                      send_timeout                    30;
                      client_max_body_size            100M;
                      reset_timedout_connection       on;
                      # Gzip module configuration
                      gzip                    on;
                      gzip_disable            "MSIE [1-6].(?!.*SV1)";
                      gzip_vary               on;
                      gzip_comp_level         6;
                      gzip_proxied            any;
                      gzip_buffers            16 8k;
                      gzip_http_version       1.1;
                      # Compress all output labeled with one of the following MIME-types.
                      gzip_types
                              application/atom+xml
                              application/javascript
                              application/json
                              application/rss+xml
                              application/vnd.ms-fontobject
                              application/x-font-ttf
                              application/x-web-app-manifest+json
                              application/xhtml+xml
                              application/xml
                              font/opentype
                              image/svg+xml
                              image/x-icon
                              text/css
                              text/plain
                              text/x-component;
                      # Put the Ip of your varnish/proxy here
                      set_real_ip_from   127.0.0.1;
                      # Put the Header that your varnish/proxy set
                      real_ip_header      X-Forwarded-For;
                      include /etc/nginx/sites-enabled/*;
              }
              

              Php-fpm

                • Installation de Php-fpm :
                apt install php5-fpm
                  • Installation des modules complémentaire :
                  apt install php5-mysql php5-xcache php-pear php5-curl php5-imap php5-mcrypt php5-xmlrpc php5-tidy
                    • Mon fichier /etc/php5/fpm/pool.d/www.conf :
                    [www]
                    user = www-data
                    group = www-data
                    listen = 127.0.0.1:9000
                    pm = ondemand
                    pm.max_children = 20
                    pm.start_servers = 2
                    pm.min_spare_servers = 1
                    pm.max_spare_servers = 3
                    pm.process_idle_timeout = 10s;
                    pm.max_requests = 200
                    pm.status_path = /status
                    request_terminate_timeout = 300s
                    rlimit_files = 65535
                    chdir = /
                    

                    MariaDB

                    • Depuis Jessy, MariaDB est présent dans les dépots :
                    apt install mariadb-server

                    Un mot de passe est demandé :
                    mariadb-01

                    mysql_secure_installation

                    Block serveur spécial WordPress avec authentification en https

                    Mon block serveur Nginx :

                    server {
                    listen      8080;
                    server_name  www.memo-linux.com;
                    root        /var/www/memo/;
                    index       index.html;
                    rewrite     ^ $scheme://memo-linux.com$request_uri permanent;
                    }
                    server {
                    listen 8080;
                    server_name memo-linux.com;
                    root /var/www/memo/;
                    access_log /var/log/nginx/access_blog-wp.log;
                    error_log /var/log/nginx/error_blog-wp.log;
                    index index.php index.phtml index.html;
                    #add_header X-Frame-Options "SAMEORIGIN";
                    rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.xml$ "/index.php?xml_sitemap=params=$2" last;
                    rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.xml\.gz$ "/index.php?xml_sitemap=params=$2;zip=true" last;
                    rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.html$ "/index.php?xml_sitemap=params=$2;html=true" last;
                    rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.html.gz$ "/index.php?xml_sitemap=params=$2;html=true;zip=true" last;
                    # Security
                            include global/security.conf;
                            location / {
                                try_files $uri $uri/ /index.php?$args;
                            }
                    # PHP-FPM
                        include global/php-fpm.conf;
                    
                    # STATICS FILES
                            location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                                    expires max;
                                    log_not_found off;
                            }
                    }
                    ###HTTPS pour la partie Admin du blog
                    {
                    listen 1443 ssl;
                      server_name memo-linux.com;
                      ssl_prefer_server_ciphers on;
                      resolver 127.0.0.1 valid=300s;
                      ssl_dhparam /etc/nginx/ssl/dhparam.pem;
                      ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-A$
                      ssl_session_cache shared:SSL:10m;
                      ssl_session_timeout 10m;
                      ssl_ecdh_curve secp384r1;
                      
                      root /var/www/memo/;
                      index index.php;
                      # Process only the requests to wp-login and wp-admin
                      location ~ /(adminer/|wp-) {
                                    #limit_req   zone=one  burst=1 nodelay;
                                    auth_basic            "Acces DENIED!";
                                    auth_basic_user_file  /home/fred/htpasswd;    
                      }
                    # Security
                        include global/security.conf;
                    location / {
                                try_files $uri $uri/ /index.php?$args;
                            }
                    # PHP-FPM
                        include global/php-fpm.conf;
                    # Redirect everything else to port 80
                    location / {
                        return 301 http://$host$request_uri;
                      }
                    }
                    

                    Mon fichier global/security.conf :

                    server_tokens off;
                    add_header X-Frame-Options "SAMEORIGIN";
                    add_header X-Content-Type-Options nosniff;
                    add_header X-XSS-Protection "1; mode=block";
                    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains" always;
                    
                    location = /favicon.ico {
                        log_not_found off;
                        access_log off;
                    }
                    location = /robots.txt {
                        allow all;
                        log_not_found off;
                        access_log off;
                    }
                    location ~ /\. {
                        deny all;
                        access_log off;
                        log_not_found off;
                    }
                    

                    Mon fichier global/php-fpm.conf:

                    location ~ \.php$ {
                     
                    location ~ /wp-(admin|login) {
                          return 301 https://$host$request_uri;
                        }
                    
                        try_files $uri =404;
                        # PHP
                        # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                        fastcgi_pass   127.0.0.1:9000;
                        fastcgi_index  index.php;
                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                        include fastcgi_params;
                        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_intercept_errors        on;
                        fastcgi_ignore_client_abort     off;
                        fastcgi_connect_timeout 60;
                        fastcgi_send_timeout 180;
                        fastcgi_read_timeout 90;
                        fastcgi_buffers 4 256k;
                        fastcgi_buffer_size 128k;
                        #fastcgi_buffers 256 16k;
                        #fastcgi_buffer_size 16k;
                        fastcgi_busy_buffers_size 256k;
                        fastcgi_temp_file_write_size 256k;
                    }
                    

                    Varnish

                    Pour les grosses sollicitations du serveur web, Varnish c’est magique !

                      • Installer varnish :
                      apt install varnish
                        • mon vcl :
                        vcl 4.0;
                        # Default backend definition. Set this to point to your content server.
                        backend default {
                            .host = "127.0.0.1";
                            .port = "8080";
                            .connect_timeout = 600s;
                            .first_byte_timeout = 600s;
                            .between_bytes_timeout = 600s;
                            .max_connections = 800;
                        }
                        # Only allow purging from specific IPs
                        acl purge {
                            "localhost";
                            "127.0.0.1";
                        }
                        # This function is used when a request is send by a HTTP client (Browser)
                        sub vcl_recv {
                                # Normalize the header, remove the port (in case you're testing this on various TCP ports)
                                set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
                        
                                # Allow purging from ACL
                                if (req.method == "PURGE") {
                                        # If not allowed then a error 405 is returned
                                        if (!client.ip ~ purge) {
                                                return(synth(405, "This IP is not allowed to send PURGE requests."));
                                        }
                                        # If allowed, do a cache_lookup -> vlc_hit() or vlc_miss()
                                        return (purge);
                                }
                        
                                # Post requests will not be cached
                                if (req.http.Authorization || req.method == "POST") {
                                        return (pass);
                                }
                        
                                # --- WordPress specific configuration
                        
                                # 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 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=[^;]+(; )?", "");
                                # Are there cookies left with only spaces or that are empty?
                                if (req.http.cookie ~ "^ *$") {
                                            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;
                                }
                        
                                # --- End of WordPress specific configuration
                         # 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;
                                }
                        
                                # --- End of WordPress specific configuration
                                # Did 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_pipe {
                                return (pipe);
                        }
                        sub vcl_pass {
                                return (fetch);
                        }
                        # 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);
                                }
                        # If the client supports compression, keep that in a different cache
                                if (req.http.Accept-Encoding) {
                                        hash_data(req.http.Accept-Encoding);
                                }
                        
                                return (lookup);
                        }
                        # This function is used when a request is sent by our backend (Nginx server)
                        sub vcl_backend_response {
                                # Remove some headers we never want to see
                                unset beresp.http.Server;
                                unset beresp.http.X-Powered-By;
                                # For static content strip all backend cookies
                                if (bereq.url ~ "\.(css|js|png|gif|jp(e?)g)|swf|ico") {
                                        unset beresp.http.cookie;
                                }
                        
                                # Only allow cookies to be set if we're in admin area
                                if (beresp.http.Set-Cookie && bereq.url !~ "^/wp-(login|admin)") {
                                        unset beresp.http.Set-Cookie;
                                } 
                                # don't cache response to posted requests or those with basic auth
                                if ( bereq.method == "POST" || bereq.http.Authorization ) {
                                        set beresp.uncacheable = true;
                        set beresp.ttl = 120s;
                                        return (deliver);
                                }
                        # don't cache search results
                                if ( bereq.url ~ "\?s=" ){
                                        set beresp.uncacheable = true;
                                        set beresp.ttl = 120s;
                                        return (deliver);
                                }
                                # only cache status ok
                                if ( beresp.status != 200 ) {
                                        set beresp.uncacheable = true;
                                        set beresp.ttl = 120s;
                                        return (deliver);
                                }
                                # A TTL of 24h
                                set beresp.ttl = 24h;
                                # Define the default grace period to serve cached content
                                set beresp.grace = 30s;
                        
                                return (deliver);
                        }
                        # The routine when we deliver the HTTP request to the user
                        # Last chance to modify headers that are sent to the client
                        sub vcl_deliver {
                                if (obj.hits > 0) {
                                        set resp.http.X-Cache = "cached";
                                } else {
                                        set resp.http.x-Cache = "uncached";
                                }
                        
                                # Remove some headers: PHP version
                                unset resp.http.X-Powered-By;
                        
                                # Remove some headers: Apache version & OS
                                unset resp.http.Server;
                        
                                # Remove some heanders: Varnish
                                unset resp.http.Via;
                                unset resp.http.X-Varnish;
                        
                                return (deliver);
                        }
                        sub vcl_init {
                                return (ok);
                        }
                        sub vcl_fini {
                                return (ok);
                        }
                        

                        Mon fichier /etc/default/varnish :

                        START=yes
                        DAEMON_OPTS="-a :80
                        -T localhost:6082
                        -f /etc/varnish/default.vcl
                        -S /etc/varnish/secret
                        -p thread_pool_add_delay=2
                        -p thread_pools=4
                        -p thread_pool_min=200
                        -p thread_pool_max=4000
                        -p cli_timeout=25
                        -p session_linger=100
                        -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"
                        

                        Cependant, la version 4 de varnish possède un bug et laisse varnish sur l’écoute du port 6081. Pour régler ce soucis, aller sur l’article : Varnish 4 forcer l’écoute sur le port 80 sous Debian 8

                        Bonus : sSMTP, Apticron, SSLh, FTP et CDN !

                        Je continu le mémo sur les services, qui gravitent autour de mon serveur LNMPV :-D

                        sSMTP

                        Installer sSMTP pour l’envoi de mail :

                        apt install ssmtp

                        Si besoin, configurer un envoi de mail à chaque connexion ssh.

                        Apticron

                        Pour être informé par mail des mises à jour sur le serveur :

                        apt install apticron

                        SSH : Désactiver l’accès root au serveur

                          • Ajouter un utilisateur au système :
                          adduser toto
                          • Editer le fichier de configuration du serveur ssh :
                          • nano /etc.ssh/sshd_config
                          • Passer à « no » la directive :
                          • PermitRootLogin no 
                          • Relance de ssh :
                          • systemctl restart ssh

                          SSLh

                          Configurer les accès au serveur ssh sur le port 443 et installer sslh :

                          apt install --no-install-recommends sslh

                          Serveur FTP

                          Pour la mise en place du serveur FTP, suivre cet article : comment installer un serveur ftp sécurisé sous debian

                          CDN

                          Pour optimiser la rapidité du chargement des pages du serveur web, il est possible de créer des CDNs: Optimisation serveur web wordpress : cdn local avec sous domaines et DNS-prefetch

                          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

                          8 commentaires

                          1. Salute,

                            envoie –> envoi
                            /etc.ss/ssd_config –> /etc/ssh/sshd_config
                            il possible –> il est possible

                            Bon alors tu passes quand à Ansible ? hi hi hi

                            Tcho !

                          2. Salut Cascador !
                            il faut que je change de clavier, la lettre « h » est morte ^^
                            Ansible ? j’y pense :-D

                          3. Merci pour le partage de ce sympathique tutoriel.
                            Malheureusement les fautes d’orthographe importantes viennent sérieusement gâcher la lecture de ton article.
                            Dommage… :(

                          4. Salut,
                            les directives « set_real_ip_from » et « real_ip_header » apparaissent 2 fois dans ton fichier nginx.conf
                            Par mesure de sécurité on peut aussi y rajouter :
                            server_tokens off;

                            Sinon bravo, très bon memo-tuto-article ;)

                          5. Oups, autant pour moi, j’avais pas vu que « server_tokens » tu l’avais mis dans global/security.conf

                          Laisser un commentaire

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