Un petit article sans prétentions pour l’installation d’un serveur web maison.
Les accès système se feront par SSH, un protocole sûr et reconnu qui équipe nativement les serveurs sous Linux.
Le mieux est la connexion via SSH + clé d’identification (plus robuste qu’un simple mot de passe). [Ajouter une clé SSH pour root]
- Générer des mots de passes fort
- Mettre à jour le système
- Installation de Fail2Ban
- Gérer les sauvegardes !
- Installation de MariaDB
- Installation de Certbot
- Installation de Apache + PHP
- Installation de Adminer
- Ajout des différents sites
Générer des mots de passes fort
Pour générer un mot de passe fort (ajustez le 32 pour la longueur de la chaine)
openssl rand -base64 32
Mettre à jour le système
Pour mettre à jour le système d’exploitation ET les logiciels installés
apt update && apt -y upgrade && apt -y autoremove
Le must est de faire une mise à jour hebdomadaire du système (pour limiter les failles de sécurité). On créé donc un fichier update-system.sh
On va créer un dossier pour stocker nos scripts pour les mises à jour
mkdir /root/update/
nano /root/update/update-system.sh
#!/bin/bash
apt update && apt -y upgrade && apt -y autoremove
On rend le script exécutable et on créé un lien pour une exécution hebdomadaire
chmod +x /root/update/update-system.sh
ln -s /root/update/update-system.sh /etc/cron.weekly/update-system
Installation de Fail2Ban
Fail2Ban va nous permettre de bloquer durant un certain temps les personnes qui tentent de s’infiltrer dans le serveur.
Cela ne résous pas tous les problèmes, mais cela peut limiter les tentatives. Il faut penser à regarder les journaux système afin éventuellement d’être plus sévère sur le temps de banissement.
apt -y install fail2ban
On rajoute une sécurisation sur le protocole SSH (pensez à modifier le 1234 en avec le port personnalisé utilisé)
nano /etc/fail2ban/jail.d/ssh.conf
[ssh]
enabled = true
port = ssh,sftp,1234
filter = sshd
logpath = /var/log/auth.log
maxretry = 4
bantime = 86400
On recharge le processus et on contrôle le bon fonctionnement
fail2ban-client reload
fail2ban-client status
Gérer les sauvegardes !
La gestion des sauvegardes est primordial pour un site web, surtout en cas de crash. Il est possible de remettre en route un nouveau serveur très rapidement, mais la restauration des données peut être problématique s’il n’y a pas de sauvegardes…
Dans une simple mesure, il faut externaliser les sauvegardes en dehors du serveur, sur un hébergement tiers :
- Scaleway (S3 Buckets, C14, FTP pour Dedibox…)
- OVH (Block Storage…)
- Etc etc
Le backup doit se faire à intervalle régulier (2x par jour par exemple) et comporter tout ce qui est nécessaire au fonctionnement rapide du site (fichiers, base de données, configuration, certificats SSL…)
Je n’utilise pas le backup de type image disque, car je veux rapidement pouvoir revenir dans un dossier, sans avoir à remonter toute l’image du backup.
On peut donc découper notre script de backup en 2 parties :
- Sauvegarde de la base de données (un export avec mysqldump)
- Sauvegarder les fichiers (une simple copie peut suffire)
Installation de duplicity
duplicity quand à lui permet de faire des backups par incrément, ce qui limite la quantité de donnée transférée. Il est aussi possible de chiffrer les backups.
Pour l’installer (la partie python3-boto est pour l’envoi sur des serveurs S3)
apt -y install duplicity python3-boto
On créé le dossier qui va contenir les scripts
mkdir /root/backup
On créé le fichier qui va exécuter le backup (et envoyer les fichiers sur un bucket)
nano /root/backup/duplicity.sh
#!/bin/bash
# On récupère les paramètres spécifiques pour le backup
source /root/backup/.source
# On donne quelques informations
echo "Backup ${SITE}"
# On supprime les vielles versions
duplicity \
remove-older-than ${KEEP_BACKUP_TIME} \
--force \
${S3_BUCKET}
# On déploie les nouvelles version
duplicity \
incr \
--exclude "${SOURCE}/logs" \
--full-if-older-than ${FULL_BACKUP_TIME} \
--no-encryption \
${SOURCE} \
${S3_BUCKET}
On rend le script exécutable
chmod +x /root/backup/duplicity.sh
Maintenant on va s’occuper du script principal : celui qui va faire les exports des bases puis exécuter le script de transfert
nano /root/backup/main.py
#!/usr/bin/env python3
import os
import sys
# Quelques configurations
# Mot de passe root pour MySQL pour faire les backup des bases
MYSQL_ROOT = "P@$$w0rd"
# Répertoires où sont les sites
SITES_PATH = "/opt/sites/"
# Identifiants pour le backup bucket S3
S3_HOST = "s3://s3.fr-par.scw.cloud/__bucketName__"
S3_ACCESS_ID = "__ACCESS-ID__"
S3_ACCESS_KEY = "__ACCESS-KEY__"
# Faire un backup SQL pour le site en question
def backupSQL(site):
# Supprimer les caractères interdits
base = site
base = (base).replace(".", "_")
base = (base).replace("-", "_")
# On indique quel site va être traité
print("SQL Base = " + base)
# Contrôle que le répertoire de destination existe
backPath = SITES_PATH + site + "/backup"
try:
os.makedirs(backPath)
except:
pass
# La commande SQL de dump
backCmd = "mysqldump -u root -p" + MYSQL_ROOT + " " + base + " | gzip -9 -c > " + backPath + "/export.sql.gz"
try:
if os.path.exists("/var/lib/mysql/" + base):
os.system(backCmd)
except:
pass
def writeSource(site):
with open("/root/backup/.source", "w") as config:
config.write('export SITE="' + site + '"' + "\n")
config.write('export AWS_ACCESS_KEY_ID="' + S3_ACCESS_ID + '"' + "\n")
config.write('export AWS_SECRET_ACCESS_KEY="' + S3_ACCESS_KEY + '"' + "\n")
config.write('export S3_BUCKET="' + S3_HOST + site + '"' + "\n")
config.write('export SOURCE="' + SITES_PATH + site + '"' + "\n")
config.write('export KEEP_BACKUP_TIME="1D"' + "\n")
config.write('export FULL_BACKUP_TIME="1D"' + "\n")
# Faire une copie des fichiers entre le serveur et l'espace de sokage distant
def backupDuplicity():
os.system("/root/backup/duplicity.sh")
#####
#
# MAIN
#
#####
for site in os.listdir(SITES_PATH):
if os.path.isdir(os.path.join(SITES_PATH, site)):
print("Backup %s" % site)
backupSQL(site)
writeSource(site)
backupDuplicity()
On rend le script exécutable et on s’assure de l’exécution chaque jour
chmod +x /root/backup/main.py
ln -s /root/backup/main.py /etc/cron.daily/backup
Installation de MariaDB
On va maintenant installer le programme pour la gestion de base de données
apt -y install mariadb-server mariadb-client
Paramétrage
On va supprimer les utilisateurs qui ne servent à rien ET donner un mot de passe root en local uniquement.
mysql_secure_installation
On va modifier la façon dont notre utilsateur root peut se connecter
echo "UPDATE user SET plugin='' WHERE User='root'; FLUSH PRIVILEGES;" | mysql -u root -p mysql
Installation de Certbot
Pour faire des certificats SSL
apt -y install certbot
Installation de Apache + PHP
On installe Apache et PHP pour l’exécution des scripts.
apt -y install apache2 php libapache2-mod-php php-mysql
On installe également des extensions
apt -y install php-gd php-xml php-zip php-curl php-mbstring php-imagick
Après chaque modification sur Apache, penser à redémarrer le service Apache :
systemctl restart apache2.service
Paramétrage PHP :
Attention à la version (ici 7.4), le chemin peut changer.
Pour savoir quelle version vous avez sur votre serveur
ls /etc/php/
sed -i 's/post_max_size = .*/post_max_size = 128M/' /etc/php/7.4/apache2/php.ini
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 128M/' /etc/php/7.4/apache2/php.ini
sed -i 's/expose_php = .*/expose_php = Off/' /etc/php/7.4/apache2/php.ini
Paramétrage Apache
sed -i 's/ServerSignature .*/ServerSignature Off/' /etc/apache2/conf-enabled/security.conf
sed -i 's/ServerTokens .*/ServerTokens Prod/' /etc/apache2/conf-enabled/security.conf
On active quelques modules pour Apache
a2enmod ssl headers rewrite proxy proxy_http
On supprime les fichiers par défaut et l’on créé un fichier vide à la place.
rm /var/www/html/index.html
touch /var/www/html/index.php
On modifie le site par défaut de Apache
nano /etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>
DocumentRoot /var/www/html
<Directory /var/www/html>
DefaultType application/x-httpd-php
DirectoryIndex index.php
AddType application/x-httpd-php .php
Require all granted
Options +FollowSymLinks +MultiViews
</Directory>
</VirtualHost>
Installation de Adminer
Pour la gestion des bases de données, beaucoup plus léger que phpMyAdmin
wget "http://www.adminer.org/latest-mysql-en.php" -O /var/www/html/adminer.php
On peut optimiser la mise à jour de Adminer
nano /root/update/update-adminer.sh
#!/bin/bash
wget "http://www.adminer.org/latest-mysql-en.php" -O /var/www/html/adminer.php
On rend le script exécutable et on créé un lien pour une exécution hebdomadaire
chmod +x /root/update/update-adminer.sh
ln -s /root/update/update-adminer.sh /etc/cron.weekly/update-adminer
Ajout des différents sites
On part sur une configuration chaque site web aura sa propre arborescence
- « backup » pour le backup de la base de données
- « conf » qui vont contenir les fichiers de configuration
- « logs » qui vont contenir les logs
- « public » pour les pages qui seront affichées sur internet
On imagine que le site à configurer est « mon-site.com »
mkdir -p /opt/sites/mon-site.com/{backup,certificates,conf,logs,public}
Pensez à redonner les droits à l’utilisateur Apache sur ces répertoires
chown -R www-data:www-data /opt/sites/mon-site.com
On va donc rajouter un fichier de configuration pour Apache (prendre un fichier par site, ce qui limitera les problèmes). Attention au fait que le fichier doit finir par « .conf »
nano /etc/apache2/sites-enabled/zzz.mon-site.com.conf
Ou on rajoute un lien direct
ln -s /opt/sites/mon-site.com/conf/apache.conf /etc/apache2/sites-enabled/zzz.mon-site.com.conf
Configuration sans certificat (http standard)
Define vHost mon-site.com
<VirtualHost *:80>
ServerName ${vHost}
ServerAlias *.${vHost}
DocumentRoot /opt/sites/${vHost}/public
<Directory /opt/sites/${vHost}/public>
DefaultType application/x-httpd-php
DirectoryIndex index.php
AddType application/x-httpd-php .php
Require all granted
AllowOverride All
Options +FollowSymLinks +MultiViews
</Directory>
ErrorLog "|/usr/bin/rotatelogs -l -f /opt/sites/${vHost}/logs/apache-error.%Y.%m.%d.log 86400"
CustomLog "|/usr/bin/rotatelogs -l -f /opt/sites/${vHost}/logs/apache-access.%Y.%m.%d.log 86400" common
LogLevel warn
</VirtualHost>
UnDefine vHost
Configuration avec certificat (https)
Define vHost mon-site.com
<VirtualHost *:80
ServerName ${vHost}
ServerAlias *.${vHost}
Redirect permanent / https://${vHost}/
</VirtualHost>
<VirtualHost *:443>
ServerName ${vHost}
ServerAlias *.${vHost}
DocumentRoot /opt/sites/${vHost}/public
<Directory /opt/sites/${vHost}/public>
DefaultType application/x-httpd-php
DirectoryIndex index.php
AddType application/x-httpd-php .php
Require all granted
AllowOverride All
Options +FollowSymLinks +MultiViews
</Directory>
ErrorLog "|/usr/bin/rotatelogs -l -f /opt/sites/${vHost}/logs/apache-error.%Y.%m.%d.log 86400"
CustomLog "|/usr/bin/rotatelogs -l -f /opt/sites/${vHost}/logs/apache-access.%Y.%m.%d.log 86400" common
LogLevel warn
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/${vHost}/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/${vHost}/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/${vHost}/fullchain.pem
SSLProtocol all -TLSv1.1 -TLSv1 -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCompression off
SSLOptions +StrictRequire
SSLCipherSuite ALL:+HIGH:!ADH:!EXP:!SSLv2:!SSLv3:!MEDIUM:!LOW:!NULL:!aNULL
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
UnDefine vHost
Ajouter un utilisateur pour la base de données. Privilégiez 1 utilisateur par base de données avec un mot de passe fort.
- dbName est le nom de la base de données
- dbUser est le nom de l’utilisateur
- dbPassword est le mot de passe pour cet utilisateur
CREATE DATABASE dbName;
GRANT ALL ON dbName.* TO 'dbUser'@'localhost' IDENTIFIED BY 'dbPassword';