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