Aller au contenu

Pare-feu : exemple de VPS avec Caddy

Configuration de bout en bout d’un VPS Ubuntu (24.04 ou 26.04) qui héberge une application web derrière Caddy. Noms génériques : domaine exemple.com, utilisateur deploy, application monapp sur le port 3000, poste d’administration 203.0.113.10.

Internet ──443/80──▶ Caddy (HTTPS, en-têtes, journaux) ──127.0.0.1:3000──▶ monapp (service systemd)
▲
UFW (22, 80, 443) + Fail2ban (sshd, caddy-auth, caddy-sondes)
  • Seuls SSH, 80 et 443 sont ouverts.
  • L’application écoute sur 127.0.0.1.
  • Caddy gère HTTPS, les en-têtes de sécurité et les journaux.
  • Fail2ban bannit les adresses qui échouent ou qui scannent.
  • Un VPS Ubuntu, une adresse IP publique, un accès initial root ou sudo.
  • Un domaine dont l’enregistrement A (et AAAA) pointe vers le VPS.
  • Une clé SSH sur le poste d’administration (voir SSH : clés).
Fenêtre de terminal
sudo apt update && sudo apt full-upgrade -y
sudo adduser deploy
sudo usermod -aG sudo deploy

Voir Ubuntu Server : actions après l’installation.

Depuis le poste d’administration :

Fenêtre de terminal
ssh-copy-id deploy@exemple.com
ssh deploy@exemple.com

Sur le VPS, en gardant une session ouverte :

Fenêtre de terminal
sudo tee /etc/ssh/sshd_config.d/00-durcissement.conf <<'CONF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
MaxAuthTries 3
AllowUsers deploy
CONF
sudo sshd -t && sudo systemctl reload ssh

Tester une nouvelle connexion avant de fermer la session. Voir SSH : durcissement du serveur.

Fenêtre de terminal
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enable

Voir UFW : prise en main. Si l’application tourne dans Docker, la publier sur 127.0.0.1 (UFW et Docker).

/etc/systemd/system/monapp.service
[Unit]
Description=Application monapp
After=network.target
[Service]
User=monapp
Group=monapp
WorkingDirectory=/srv/monapp/app
EnvironmentFile=/etc/monapp.env
ExecStart=/usr/bin/node src/serveur.js
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/monapp
[Install]
WantedBy=multi-user.target
Fenêtre de terminal
sudo systemctl daemon-reload
sudo systemctl enable --now monapp
curl -I http://127.0.0.1:3000/
sudo ss -tlnp | grep ':3000'

Voir systemd : écrire un service et durcissement.

Installation depuis le dépôt officiel (voir Caddy : installation) :

Fenêtre de terminal
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
sudo mkdir -p /var/log/caddy && sudo chown caddy:caddy /var/log/caddy

/etc/caddy/Caddyfile :

{
email admin@exemple.com
}
(securite) {
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}
exemple.com {
import securite
encode zstd gzip
log {
output file /var/log/caddy/acces.log {
roll_size 10MiB
roll_keep 5
}
format json
}
basic_auth /admin/* {
admin HASH_GENERE_PAR_CADDY_HASH_PASSWORD
}
reverse_proxy 127.0.0.1:3000
}
www.exemple.com {
redir https://exemple.com{uri} permanent
}

Générer le hash du mot de passe (saisie masquée) et le coller dans le Caddyfile :

Fenêtre de terminal
caddy hash-password

Valider et appliquer :

Fenêtre de terminal
sudo -u caddy caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Caddy obtient le certificat HTTPS au premier accès, si le DNS pointe vers le VPS et si les ports 80 et 443 sont joignables. Plus d’exemples : Caddy : exemples réutilisables.

Fenêtre de terminal
sudo apt install fail2ban

Filtres Caddy (voir Fail2ban : jails et filtres) :

Fenêtre de terminal
sudo tee /etc/fail2ban/filter.d/caddy-auth.conf <<'CONF'
[Definition]
failregex = ^.*"client_ip":"<ADDR>".*"status":(?:401|403),
datepattern = "ts":{EPOCH}
ignoreregex =
CONF
sudo tee /etc/fail2ban/filter.d/caddy-sondes.conf <<'CONF'
[Definition]
failregex = ^.*"client_ip":"<ADDR>".*"uri":"[^"]*(?:/\.env|/\.git|wp-login\.php|xmlrpc\.php|phpmyadmin|/admin\.php|/cgi-bin)[^"]*".*"status":404,
datepattern = "ts":{EPOCH}
ignoreregex =
CONF

Jails (remplacer 203.0.113.10 par l’adresse du poste d’administration) :

Fenêtre de terminal
sudo tee /etc/fail2ban/jail.local <<'CONF'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime.increment = true
[sshd]
enabled = true
maxretry = 3
mode = aggressive
[caddy-auth]
enabled = true
port = http,https
filter = caddy-auth
logpath = /var/log/caddy/acces.log
maxretry = 5
[caddy-sondes]
enabled = true
port = http,https
filter = caddy-sondes
logpath = /var/log/caddy/acces.log
maxretry = 3
bantime = 1d
CONF
sudo systemctl restart fail2ban
sudo fail2ban-client status
Fenêtre de terminal
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Sur le VPS :

Fenêtre de terminal
sudo ss -tlnp
sudo ufw status verbose
sudo fail2ban-client status
sudo systemctl is-active caddy monapp fail2ban
sudo journalctl -u caddy -n 20 --no-pager

Seuls 22, 80 et 443 doivent écouter publiquement ; le port 3000 reste sur 127.0.0.1.

Depuis une autre machine :

Fenêtre de terminal
curl -I https://exemple.com/ # 200 et en-têtes de sécurité
curl -I http://exemple.com/ # redirection vers HTTPS
curl -u admin:mauvais -I https://exemple.com/admin/ # 401
nmap -Pn -p 22,80,443,3000,3306,5432,6379 exemple.com

Sauvegarder la base de données (PostgreSQL, MariaDB, SQLite), les fichiers de l’application et /etc, tester la restauration et copier hors du VPS.

Symptôme Piste
Pas de certificat HTTPS DNS non propagé, ports 80/443 fermés (UFW, hébergeur) : sudo journalctl -u caddy -n 50
Caddy échoue avec permission denied sudo chown caddy:caddy /var/log/caddy/*
502 Bad Gateway Application arrêtée ou mauvais port : systemctl status monapp
Plus d’accès SSH après le durcissement Console : retirer /etc/ssh/sshd_config.d/00-durcissement.conf (voir SSH : dépannage)
Fail2ban ne bannit pas depuis le serveur ignoreself : essayer depuis une autre machine
Même adresse pour tous les visiteurs Caddy derrière un proxy : trusted_proxies
Port 3000 joignable de l’extérieur L’application écoute sur 0.0.0.0 : la faire écouter sur 127.0.0.1

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Les étapes sont identiques sur les deux versions. À noter sur 26.04 : OpenSSH 10 journalise sous sshd-session, PerSourcePenalties est actif par défaut et /tmp est en mémoire.