Sécuriser son infrastructure Docker et Nginx en production
Introduction
Une application déployée en production est immédiatement exposée à internet. Sans mesures de sécurité, elle devient une cible pour les bots, les scanners automatiques et les attaquants. Dans cet article, on sécurise chaque couche de l'infrastructure : SSH, Nginx, MongoDB et les containers Docker.
Tout ce qui est décrit ici a été appliqué sur le serveur qui héberge ce blog.
1. Bloquer les attaques SSH avec Fail2ban
Dès qu'un serveur est en ligne, des bots tentent de se connecter en SSH par force brute. Des milliers de tentatives par jour avec des combinaisons d'identifiants courants.
Installation :
sudo apt install -y fail2ban
Configuration :
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
Démarrage :
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
Vérification :
sudo fail2ban-client status sshd
En quelques heures, Fail2ban bannit automatiquement les IPs malveillantes. Sur notre serveur, plusieurs IPs ont été bannies en moins d'une heure après l'installation.
2. Sécuriser MongoDB
Par défaut, MongoDB démarre sans authentification. N'importe qui ayant accès au serveur peut lire et modifier toutes les données.
Créer les utilisateurs :
docker exec -it mongodb mongosh
use admin
db.createUser({
user: "mongoAdmin",
pwd: "MotDePasseFort",
roles: ["root"]
})
use mabase
db.createUser({
user: "appUser",
pwd: "AutreMotDePasse",
roles: [{ role: "readWrite", db: "mabase" }]
})
Activer l'authentification en recréant le container avec deux changements importants :
docker run -d \
--name mongodb \
-p 127.0.0.1:27017:27017 \
--restart always \
-v /home/app/mongo-data:/data/db \
mongo:7 --auth
-p 127.0.0.1:27017:27017 rend MongoDB accessible uniquement depuis le serveur lui-même. --auth rend l'authentification obligatoire pour toutes les connexions.
Mettre à jour l'URI de connexion dans le fichier .env de l'application :
MONGO_URI=mongodb://appUser:AutreMotDePasse@MONGO_IP:27017/mabase?authSource=mabase
3. Sauvegarder MongoDB automatiquement
Une base de données non sauvegardée est une base de données perdue.
Créer le script de sauvegarde :
sudo nano /home/app/backup-mongo.sh
#!/bin/bash
DATE=$(date +%Y-%m-%d_%H-%M)
BACKUP_DIR="/home/app/backups/mongodb"
docker exec mongodb mongodump \
-u appUser -p AutreMotDePasse \
--authenticationDatabase mabase \
--db mabase \
--out /tmp/backup-$DATE
docker cp mongodb:/tmp/backup-$DATE $BACKUP_DIR/
find $BACKUP_DIR -type d -mtime +7 -exec rm -rf {} +
echo "Sauvegarde terminée : $DATE"
sudo chmod +x /home/app/backup-mongo.sh
Automatiser avec cron, chaque nuit à 2h :
sudo crontab -e
0 2 * * * /home/app/backup-mongo.sh >> /var/log/backup-mongo.log 2>&1
4. Surveiller les containers Docker
Ce script vérifie toutes les 5 minutes que les containers tournent et les redémarre automatiquement si nécessaire.
sudo nano /home/app/monitor-containers.sh
#!/bin/bash
CONTAINERS=("mon-backend" "mon-frontend" "mongodb")
for container in "${CONTAINERS[@]}"; do
if ! docker ps --format '{{.Names}}' | grep -q "^$container$"; then
echo "Container $container arrete, redemarrage en cours..."
docker start $container
fi
done
sudo chmod +x /home/app/monitor-containers.sh
Ajouter au cron :
*/5 * * * * /home/app/monitor-containers.sh >> /var/log/monitor-containers.log 2>&1
5. Sécuriser Nginx
Masquer les informations techniques dans /etc/nginx/nginx.conf :
http {
server_tokens off;
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
}
server_tokens off masque la version de Nginx dans les headers HTTP. Les directives limit_req_zone définissent des zones de rate limiting pour limiter le nombre de requêtes par IP.
Ajouter les headers de sécurité dans la configuration du site :
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_hide_header X-Powered-By;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
}
location /api/ {
limit_req zone=api burst=10 nodelay;
proxy_pass http://localhost:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_hide_header X-Powered-By;
}
X-Content-Type-Options nosniff empêche le navigateur de deviner le type MIME d'une réponse. X-Frame-Options DENY empêche le clickjacking en interdisant l'intégration du site dans une iframe. X-XSS-Protection active la protection XSS du navigateur.
Bloquer les ports applicatifs depuis l'extérieur :
sudo iptables -A INPUT -p tcp --dport 3000 -j DROP
sudo iptables -A INPUT -p tcp --dport 8000 -j DROP
sudo apt install iptables-persistent
sudo netfilter-persistent save
6. Résumé
| Couche | Mesure | Effet |
|---|---|---|
| SSH | Fail2ban | Attaques par force brute bloquées automatiquement |
| MongoDB | Authentification obligatoire | Base de données inaccessible sans credentials |
| MongoDB | Accès localhost uniquement | Port 27017 non exposé sur internet |
| MongoDB | Sauvegarde automatique nocturne | Données récupérables en cas de problème |
| Docker | Surveillance automatique | Containers redémarrés si ils tombent |
| Nginx | Rate limiting | Protection contre les attaques DDoS basiques |
| Nginx | Headers de sécurité | Protection XSS, clickjacking, MIME sniffing |
| Nginx | server_tokens off | Version de Nginx masquée |
| Ports | iptables | Ports 3000 et 8000 inaccessibles depuis internet |
Conclusion
La sécurité n'est pas une étape qu'on fait une fois, c'est un processus continu. Ces mesures couvrent les bases pour un projet en production, mais il reste toujours plus à faire : audit régulier des logs, mise à jour des dépendances, tests de pénétration.
Le plus important est de commencer. Un serveur non sécurisé est une question de quand, pas de si.