Un tutoriel complet, accessible et technique, pour construire une stack web sécurisée et performante avec une Debian12 sur un ton sympa mais rigoureux. Bon c’est un peu long mais faut bien ça
📁 Partie 1 : Prérequis & Installation de base
Matériel requis :
- Une VM Debian 12 (chez toi ou chez un hébergeur)
- Accès root ou sudo
Installation des services de base :
udo apt update && sudo apt upgrade -y
sudo apt install nginx apache2 php php-fpm mariadb-server unzip curl sudo ufw fail2ban certbot python3-certbot-nginx -y
Active les services au démarrage :
sudo systemctl enable nginx apache2 php-fpm mariadb
🔑 Partie 2 : Sécuriser l’accès SSH dès le départ
1. Générer une clé SSH (sur le client)
ssh-keygen -t rsa -b 4096 -C "[email protected]"
2. Copier la clé publique sur la VM
ssh-copy-id utilisateur@ip_serveur
3. Désactiver l’accès par mot de passe
Éditer /etc/ssh/sshd_config :
PasswordAuthentication no
PermitRootLogin no
Port 2222
Redémarrer le service SSH :
sudo systemctl restart ssh
N’oublie pas d’ouvrir le port choisi dans UFW :
sudo ufw allow 2222/tcp
🔁 Partie 3 : NGINX en reverse proxy devant Apache
NGINX est plus performant pour servir les fichiers statiques et gérer les connexions HTTP/HTTPS. Apache gère la logique PHP.
Configuration NGINX pour HTTPS avec Certbot :
sudo certbot --nginx -d ton-domaine.com
Blocs NGINX (extrait type) :
server {
listen 80;
server_name ton-domaine.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name ton-domaine.com;
ssl_certificate /etc/letsencrypt/live/ton-domaine.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ton-domaine.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_hide_header Server;
add_header Server "LeNomQueTuVeux" always;
}
}
Apache doit écouter sur 8080 :
sudo sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
sudo sed -i 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-available/000-default.conf
sudo systemctl restart apache2
🔐 Partie 4 : Sécuriser NGINX (headers, GZIP, anti-scan)
Activer GZIP :
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript font/woff2;
Headers de sécurité :
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'self';" always;
Masquer le header Server :
proxy_hide_header Server;
add_header Server "SecuSlice" always;
🚪 Partie 5 : Masquer Apache et sécuriser le backend
Cacher la version Apache
Fichier : /etc/apache2/conf-available/security.conf
ServerTokens Prod
ServerSignature Off
Protéger wp-config.php
<Files wp-config.php>
Require all denied
</Files>
⚡ Partie 6 : Installer et configurer Fail2Ban
sudo apt install fail2ban -y
Jail pour SSH
Fichier : /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 4
bantime = 3600
findtime = 600
Jail pour NGINX + WordPress
Fichier /etc/fail2ban/filter.d/nginx-wp-login.conf
[Definition]
failregex = <HOST> -.*POST /wp-login.php
ignoreregex =
Puis jail local :
[nginx-wp-login]
enabled = true
filter = nginx-wp-login
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 3600
findtime = 600
sudo systemctl restart fail2ban
💾 Partie 7 : WordPress et sécurité PHP
Fichier wp-config.php
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);
define('DISALLOW_FILE_EDIT', true);
define('AUTOMATIC_UPDATER_DISABLED', true);
🧩 Partie 8 : Bonus – Masquer totalement la stack aux scanners
Même avec des protections classiques, certains scanners comme Wappalyzer, WhatWeb ou Shodan peuvent encore détecter la stack serveur. Voici quelques astuces pour obscurcir totalement ta stack web :
1. Supprimer ou modifier les en-têtes révélateurs via NGINX
proxy_hide_header Server;
proxy_hide_header X-Powered-By;
add_header Server "leNomQueTuVeux" always;
2. Désactiver les headers PHP
Dans /etc/php/8.2/fpm/php.ini :
expose_php = Off
Et relancer PHP :
sudo systemctl restart php8.2-fpm
3. Supprimer les headers Apache
Dans /etc/apache2/conf-available/security.conf :
ServerTokens Prod
ServerSignature Off
4. Ajouter un header personnalisé (fausse piste)
add_header X-Powered-By "Hugo on Netlify";
5. Filtrer les requêtes suspectes (anti-scanner soft)
location ~* "(w00tw00t|phpmyadmin|xmlrpc|etc)" {
return 444;
}
6. Analyser avec Wappalyzer + WhatWeb + Shodan
Teste ton site avec :
whatweb https://ton-site.com
Et surveille les entêtes visibles avec :
curl -I https://ton-site.com
🧠 Bonus : utilise Cloudflare ou un CDN avec proxy actif pour cacher complètement ton IP publique et stack réelle.
🤖 Partie 9 : Script de post-installation automatique
Voici un exemple de script Bash pour automatiser les tâches abordées dans ce tutoriel :
Fichier : setup-server.sh
#!/bin/bash
# Vérification des droits
if [ "$EUID" -ne 0 ]; then
echo "Ce script doit être exécuté en tant que root."
exit 1
fi
# Variables
SSH_PORT=2222
DOMAIN="ton-domaine.com"
EMAIL="[email protected]"
# MàJ système
apt update && apt upgrade -y
# Installation paquets de base
apt install -y nginx apache2 php php-fpm mariadb-server unzip curl sudo ufw fail2ban certbot python3-certbot-nginx
# Configuration SSH
sed -i "s/#Port 22/Port $SSH_PORT/" /etc/ssh/sshd_config
sed -i "s/PermitRootLogin yes/PermitRootLogin no/" /etc/ssh/sshd_config
sed -i "s/#PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
systemctl restart ssh
# Pare-feu UFW
ufw allow $SSH_PORT/tcp
ufw allow 'Nginx Full'
ufw enable
# Config Apache pour port 8080
sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
sed -i 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-available/000-default.conf
systemctl restart apache2
# Certificat SSL
certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m $EMAIL
# Fail2Ban config SSH
cat <<EOF > /etc/fail2ban/jail.local
[sshd]
enabled = true
port = $SSH_PORT
logpath = /var/log/auth.log
maxretry = 4
bantime = 3600
findtime = 600
EOF
systemctl restart fail2ban
echo "Installation terminée. Pensez à vérifier manuellement les configurations NGINX/Apache pour personnaliser les headers et la sécurité."
💡 Ce script est un point de départ. Tu peux le rendre interactif, lier des fichiers .conf, ou le coupler à Ansible ou Makefile selon ton niveau.
🚀 Partie 10 : Déploiement CI/CD simplifié avec Git + rsync
🔧 Script d’intégration Git complet (local ➜ serveur via Git hook)
Ce script te permet de déployer automatiquement ton site sur la VM dès qu’un git push est effectué.
Étape 1 : initialisation sur le serveur
cd /var/www/ton-site
sudo git init --bare /var/repo/mon-site.git
Étape 2 : post-receive hook Crée le hook /var/repo/mon-site.git/hooks/post-receive :
#!/bin/bash
GIT_WORK_TREE=/var/www/ton-site git checkout -f
chown -R www-data:www-data /var/www/ton-site
Puis rends-le exécutable :
chmod +x /var/repo/mon-site.git/hooks/post-receive
Étape 3 : ajout du remote sur ton poste local
git remote add prod ssh://utilisateur@ip:/var/repo/mon-site.git
git push prod main
À chaque git push prod main, le serveur mettra automatiquement à jour /var/www/ton-site.
🔒 Pense à sécuriser l’accès SSH avec clé + port non standard (ex. 2222)
Tu peux combiner cette méthode avec Fail2Ban + rsync + actions GitHub pour une solution complète !
Tu veux mettre à jour ton site web ou ton application WordPress sans te connecter en SSH à chaque fois ? On met en place un petit pipeline de déploiement simple, efficace, et sécurisé.
🧱 Méthode 1 : Déploiement manuel via git pull
Sur ton serveur :
cd /var/www/ton-site
sudo git clone [email protected]:toncompte/ton-repo.git .
Ensuite, quand tu veux déployer :
ssh utilisateur@serveur 'cd /var/www/ton-site && git pull origin main'
🧱 Méthode 2 : Déploiement automatisé avec rsync
Sur ta machine locale ou un runner CI :
rsync -avz --delete -e "ssh -p 2222" ./mon-projet/ utilisateur@ip:/var/www/ton-site/
💡 Utilise –exclude pour ignorer les fichiers non pertinents (comme .git, .env, node_modules, etc.)
🔐 Clé SSH pour Git et rsync
Génère une clé et ajoute-la à ta VM (si pas déjà fait) :
ssh-keygen -t rsa -b 4096
ssh-copy-id -p 2222 utilisateur@serveur
🛠️ Exemple de GitHub Actions pour déployer en rsync :
Option 1 : GitHub Actions
name: Deploy via rsync
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy via rsync
run: |
rsync -avz --delete -e "ssh -p 2222 -o StrictHostKeyChecking=no" ./ utilisateur@ip:/var/www/ton-site/
env:
RSYNC_PASSWORD: ${{ secrets.RSYNC_PASSWORD }}
Option 2 : GitLab CI/CD
Voici un .gitlab-ci.yml pour déployer automatiquement sur ta VM via rsync :
stages:
- deploy
deploy_production:
stage: deploy
script:
- rsync -avz --delete -e "ssh -p 2222 -o StrictHostKeyChecking=no" ./ utilisateur@ip:/var/www/ton-site/
only:
- main
💡 Ajoute ta clé privée dans GitLab CI/CD Settings > Variables (par exemple SSH_PRIVATE_KEY) et utilise un script avant rsync pour l’écrire dans ~/.ssh/id_rsa avec les bons droits :
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
🧪 Test de déploiement
- Push sur ton repo Git
- GitHub déclenche l’action
- Le serveur reçoit les fichiers (test via NGINX ou curl)
🐳 Partie 11 : Packaging Docker de la stack
Tu veux déployer ta stack WordPress + NGINX + Apache + Fail2Ban de façon portable et réutilisable ? Docker est ton ami !
📦 Exemple de structure de projet Dockerisé
.
├── docker-compose.yml
├── nginx
│ └── default.conf
├── apache
│ └── site.conf
├── wordpress
│ └── Dockerfile
├── fail2ban
│ └── jail.local
📄 docker-compose.yml minimal
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./wordpress:/var/www/html
depends_on:
- apache
apache:
image: php:8.2-apache
volumes:
- ./wordpress:/var/www/html
- ./apache/site.conf:/etc/apache2/sites-enabled/000-default.conf
mariadb:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: wordpress
fail2ban:
image: crazymax/fail2ban
volumes:
- ./fail2ban/jail.local:/data/jail.local
network_mode: "host"
cap_add:
- NET_ADMIN
- NET_RAW
🚀 Démarrage
docker compose up -d
Tu peux ensuite intégrer Watchtower, Traefik, ou créer un .env pour automatiser plus loin encore.
📁 Annexe : Fichiers à télécharger
- 🔧 setup-server.sh – Script de post-installation
- 📦 docker-compose.yml, Dockerfile, et confs disponibles sur demande ou via Git (à préparer)
🏁 Conclusion & Suite possible
Tu as maintenant une stack web moderne, sécurisée, optimisée et déployable automatiquement !
👉 Nous verrons dans les prochains articles :
- Intégration d’un WAF (ModSecurity)
- Centralisation des logs (Graylog, ELK)
- Monitoring applicatif (uptime + performance)