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 :
Tout ce petit monde est orchestré par la distribution GNU/Linux Debian 8 Jessie.
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é :
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
PermitRootLogin no
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
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 !
Salut Cascador !
il faut que je change de clavier, la lettre « h » est morte ^^
Ansible ? j’y pense :-D
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… :(
:oops:
You are welcome hi hi hi : http://www.laurent-napias.com/post/2014/10/12/intro
Tcho !
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 ;)
Oups, autant pour moi, j’avais pas vu que « server_tokens » tu l’avais mis dans global/security.conf
Merci phil ! :-D