Configurer une VM Debian 12 comme un pro : NGINX + Apache + Fail2Ban

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)

Configurer une VM Debian 12 comme un pro : NGINX + Apache + Fail2Ban
Partager cet article : Twitter LinkedIn WhatsApp

🖋️ Publié sur SecuSlice.com

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut