Comme vous le savez, je me suis fait hacké mon site pendant les “grandes vacances”. Cette petite contrariété m’a fait faire dans l’urgence une chose que j’avais planifier depuis un certain moment: la migration du Blog de Nicolargo sur un serveur privé virtuel de Gandi.
Nous allons donc voir dans ce billet comment installer, sécuriser et optimiser une blog WordPress sur une serveur privé virtuel !
Commencons par le commencement
Il faut d’abord choisir les performances initiales du serveur virtuel. J’ai utilisé le serveur que j’avais créé il y a quelques mois. C’est l’entrée de gamme, c’est à dire 1 part correspondant aux spécifications suivantes:
- 1/64ème des capacités globales du serveur, soit environ 1 GHz
- 1/64ème des 16 Go de mémoire du serveur, soit 256 Mo de RAM garantie avec un burst possible à 2 Go (+512 Mo de Swap)
- 1/64ème des capacités réseau, soit 5 Mbits dédiés
- 1/64ème du disque réservé au serveur, soit 5 Go de disque data (+3 Go pour le système)
Comme nous allons le voir plus tard cette configuration n’est pas adapté pour un blog avec un trafic relativement important (environ 1 00 000 pages vues par mois) . Je suis donc rapidement passé à la configuration suivante:
Nous disposons donc d’un accès SSH (root) sur le serveur,. Après une mise à jour du système d’ecploitation (Ubuntu Server 9.04) nous pouvons enfin commencer les choses sérieuses…
Installation du serveur Web Apache
On commence par installer le serveur:
sudo aptitude install apache2
Puis on active les modules qui vont nous permettre d’optimiser les performances de WordPress:
sudo a2enmod deflate
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod expires
Il est également possible d’adapter la configuration des processus Apache (nombre de process, nombre maximal de client par process..) en modifiant le fichier /etc/apache2/apache2.conf:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 40
MaxRequestsPerChild 2000
</IfModule>
StartServers permet de configurer le nombre de processus Apache lancés au démarrage.
MinSpareServers et MaxSpareServers donnent respectivement le nombre minimum et maximum de processus Apache selon les besoins.
MaxClients permet de configurer le nombre de clients (au sens session TCP) pouvant se connecter simultanément à un processus Apache. Pour des sites dynamiques (comme WordPress) il est plutôt conseillé de ne pas avoir une valeur trop élevée afin d’éviter les swap mémoire sur disque.
Une solution proposée sur ce blog pour calculer la valeur MaxClients est la suivantes: Il faut d’abord regarder combien de mémoire prend un processus apache2 (par exemple avec la commande top), puis diviser la mémoire système disponible par cette valeur. Par exemple, pour un serveur disposant de 1 Go de RAM (1000 Mo) et dont chaque processus Apache occupe 25 Mo, on a le calcul suivant:
MaxClients = 1000 / 25 = 40
Enfin MaxRequestsPerChild permet de définir le nombre maximal de requête qu’un processus peut prendre en charge avant d’être tué. Il est conseillé de mettre une valeur différente de 0 (le processus n’est alors jamais tué) pour éviter qu’une processus ne consomme trop de mémoire si un problème arrive.
On passe ensuite aux directives KeepAlive qui permette de configurer finement le temps de vie des sessions TCP. Pour un site dynamique comportant un nombre important d’images et de JS (comme mon blog), les valeurs suivants sont un bon point de départ:
# KeepAlive: Whether or not to allow persistent connectionsKeepAlive On# MaxKeepAliveRequests: The maximum number of requests to allowMaxKeepAliveRequests 200# KeepAliveTimeout: Number of seconds to wait for the next requestKeepAliveTimeout 5# Timeout: The number of seconds before receives and sends time out.Timeout 50
Enfin on relance le serveur pour que la configuration soit prise en compte:
sudo /etc/init.d/apache2 restart
Installation de MySQL
On commence par installer le serveur MySQL:
sudo aptitude install mysql-server
Puis on créé une base de donnée et un compte utilisateur associé qui va servir pour WordPress:
mysql -u adminusername -p
mysql> CREATE DATABASE databasename;
Query OK, 1 row affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON databasename.* TO “wordpressusername”@”hostname”
-> IDENTIFIED BY “password”;
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
mysql> EXIT
On peut optimiser la base de donnée en éditant le fichier /etc/mysql/my.cnf:
query_cache_type = 1
query_cache_limit = 2M
query_cache_size = 32M
Ne pas oublier de relancer le serveur MySQL pour prendre en compte les modifications.
Installation de WordPress
Depuis le hack de mon blog, j’ai décidé de travailler directement avec la version SVN de WordPress afin de disposer au plus vite des patchs de sécurité. Une fois de le répertoire racine de votre site Web (/home/nicolargo/web/blog dans mon cas), il faut saisir la commande suivante:
svn co http://core.svn.wordpress.org/trunk/ .
On ajoute ensuite la ligne suivante dans le fichier .htaccess afin de rendre invisible les sous répertoires .svn:
RewriteRule ^(.*/)?\.svn/ – [F,L]
Puis on finalise l’installation en suivant la fameuse installation de WordPress en 5 minutes.
Il est ensuite conseillé de supprimer le fichier wp-admin/install.php (qui ne servira plus une fois l’installation faite):
rm wp-admin/install.php
Sécurisation de WordPress
Cette sécurisation passe par deux étapes. Une première est de protéger le blog à l’aide du fichier .htaccess. La seconde est d’installer des plugins de sécurité qui vont nous permettre de faire régulièrement des audits sur notre serveur.
Pour le fichier .htaccess, j’utilise la base suivante:
# MAIN
RewriteEngine On
ServerSignature Off
Options All -Indexes
Options +FollowSymLinks
# SVN protect
RewriteRule ^(.*/)?\.svn/ – [F,L]
# Secure .htaccess
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
# Secure wp-config.php
<Files wp-config.php>
Order Deny,Allow
Deny from all
</Files>
# FILTER REQUEST
<IfModule mod_rewrite.c>
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# BEGIN Expire Header
<FilesMatch “\.(ico|jpg|jpeg|png|gif|js|css|swf)$”>
ExpiresDefault “access plus 2 hours”
</FilesMatch>
# BLACKLIST CANDIDATES
<Limit GET POST PUT>
Order Allow,Deny
Allow from all
Deny from 75.126.85.215 “# blacklist candidate 2008-01-02 = admin-ajax.php attack “
Deny from 128.111.48.138 “# blacklist candidate 2008-02-10 = cryptic character strings “
Deny from 87.248.163.54 “# blacklist candidate 2008-03-09 = block administrative attacks “
Deny from 84.122.143.99 “# blacklist candidate 2008-04-27 = block clam store loser “
Deny from 210.210.119.145 “# blacklist candidate 2008-05-31 = block _vpi.xml attacks “
Deny from 66.74.199.125 “# blacklist candidate 2008-10-19 = block mindless spider running “
Deny from 203.55.231.100 “# 1048 attacks in 60 minutes”
Deny from 24.19.202.10 “# 1629 attacks in 90 minutes”
</Limit>
# QUERY STRING EXPLOITS
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} \.\.\/ [NC,OR]
RewriteCond %{QUERY_STRING} boot\.ini [NC,OR]
RewriteCond %{QUERY_STRING} tag\= [NC,OR]
RewriteCond %{QUERY_STRING} ftp\: [NC,OR]
RewriteCond %{QUERY_STRING} http\: [NC,OR]
RewriteCond %{QUERY_STRING} https\: [NC,OR]
RewriteCond %{QUERY_STRING} mosConfig [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>|’|”|;|\?|\*).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(%22|%27|%3C|%3E|%5C|%7B|%7C).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(%0|%A|%B|%C|%D|%E|%F|127\.0).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(globals|encode|config|localhost|loopback).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(request|select|insert|union|declare|drop).* [NC]
RewriteRule ^(.*)$ – [F,L]
</IfModule>
# CHARACTER STRINGS
<IfModule mod_alias.c>
# BASIC CHARACTERS
RedirectMatch 403 \,
RedirectMatch 403 \:
RedirectMatch 403 \;
RedirectMatch 403 \=
RedirectMatch 403 \@
RedirectMatch 403 \[
RedirectMatch 403 \]
RedirectMatch 403 \^
RedirectMatch 403 \`
RedirectMatch 403 \{
RedirectMatch 403 \}
RedirectMatch 403 \~
RedirectMatch 403 \”
RedirectMatch 403 \$
RedirectMatch 403 \<
RedirectMatch 403 \>
RedirectMatch 403 \|
RedirectMatch 403 \.\.
RedirectMatch 403 \/\/
RedirectMatch 403 \%0
RedirectMatch 403 \%A
RedirectMatch 403 \%B
RedirectMatch 403 \%C
RedirectMatch 403 \%D
RedirectMatch 403 \%E
RedirectMatch 403 \%F
RedirectMatch 403 \%22
RedirectMatch 403 \%27
RedirectMatch 403 \%28
RedirectMatch 403 \%29
RedirectMatch 403 \%3C
RedirectMatch 403 \%3E
RedirectMatch 403 \%3F
RedirectMatch 403 \%5B
RedirectMatch 403 \%5C
RedirectMatch 403 \%5D
RedirectMatch 403 \%7B
RedirectMatch 403 \%7C
RedirectMatch 403 \%7D
# COMMON PATTERNS
Redirectmatch 403 \_vpi
RedirectMatch 403 \.inc
Redirectmatch 403 xAou6
Redirectmatch 403 db\_name
Redirectmatch 403 select\(
Redirectmatch 403 convert\(
Redirectmatch 403 \/query\/
RedirectMatch 403 ImpEvData
Redirectmatch 403 \.XMLHTTP
Redirectmatch 403 proxydeny
RedirectMatch 403 function\.
Redirectmatch 403 remoteFile
Redirectmatch 403 servername
Redirectmatch 403 \&rptmode\=
Redirectmatch 403 sys\_cpanel
RedirectMatch 403 db\_connect
RedirectMatch 403 doeditconfig
RedirectMatch 403 check\_proxy
Redirectmatch 403 system\_user
Redirectmatch 403 \/\(null\)\/
Redirectmatch 403 clientrequest
Redirectmatch 403 option\_value
RedirectMatch 403 ref\.outcontrol
# SPECIFIC EXPLOITS
RedirectMatch 403 errors\.
RedirectMatch 403 config\.
RedirectMatch 403 include\.
RedirectMatch 403 display\.
RedirectMatch 403 register\.
Redirectmatch 403 password\.
RedirectMatch 403 maincore\.
RedirectMatch 403 authorize\.
Redirectmatch 403 macromates\.
RedirectMatch 403 head\_auth\.
RedirectMatch 403 submit\_links\.
RedirectMatch 403 change\_action\.
Redirectmatch 403 com\_facileforms\/
RedirectMatch 403 admin\_db\_utilities\.
RedirectMatch 403 admin\.webring\.docs\.
Redirectmatch 403 Table\/Latest\/index\.
</IfModule>
On passe ensuite à l’installation des plugins de sécurité suivants:
Le premier (Secure WordPress) permet d’effectuer simplement et automatiquement une liste d’actions essentielle à la sécurité de votre site:
Le second (WP Security Scan) effectue régulièrement un scan de l’arborescence de votre site et remonte dans l’interface d’administration de WordPress les fichiers modifiés.
Optimisation de WordPress
Pour l’optimisation des performance du blog, je me base sur l’indispensable plugin W3 Total Cache. Bien qu’il ne se limite pas qu’a cela, il permet de mettre facilement en place un système de cache ou les pages sont directement gérées en mémoire RAM plutôt qu’en lecture sur le disque dur. J’ai choisi de coupler W3 Total Cache avec Memcached (un cache mémoire sous licence libre).
L’installation de Memcached est des plus simple:
sudo aptitude install libcache-memcached-perl memcached
Si votre serveur est protégé par un Firewall (ce que je conseille évidemment…), il ne faut pas oublier d’ajouter la règle suivante:
iptables -A OUTPUT -p tcp –destination 127.0.0.1 –dport 11211 -j ACCEPT
La configuration de W3 Total Cache se fait via l’onglet Performance / General settings:
Une dernière optimisation consiste à installer le plugin WP-DBManager et de suivre régulièrement (tout les mois environ) les procédures d’optimisation (onglet Database).
Test et validation
Pour tester les performances de votre serveur, vous pouvez utiliser la commande suivante:
ab -t 30 -c 5 http://votreblog.com/
L’information intéressante se trouve sur la ligne:
Requests per second: 159.46 [#/sec] (mean)
Il est également important de surveiller les paramètres suivant: charge CPU, mémoire RAM disponible, accès disque et réseau. Gandi propose pour cela une interface d’administration Web avec un système de sonde permettant de remonter des alertes par mail ou même d’augmenter dynamiquement le nombre de part lors d’un pic de trafic.
Personnellement, je surveille mon serveur via Nagios mais cela sera le sujet d’un prochain billet…