AKA Déployer une stack web et transactionnelle propre avec Cloudflare, Mailtrap & Nginx Multi-Vhosts
Quand on héberge plusieurs services web sur un même serveur, concilier isolation des hôtes virtuels (multi-vhosts), étanchéité du serveur d'origine derrière un CDN et délivrabilité des e-mails transactionnels devient vite un casse-tête si l'on ne pose pas un cadre strict dès le départ.
L'objectif ici n'est pas de faire du bricolage, mais d'établir un standard reproductible et durci :
| Flux | Trajet technique | Rôle & Confidentialité |
|---|---|---|
| Web entrant (HTTPS) | Visiteur→Cloudflare Edge (WAF/Proxy)→Nginx Origin | L'IP réelle du serveur n'est jamais exposée publiquement. |
| Mail sortant (SMTP) | App→Mailtrap SMTP →Destinataire | L'IP source dans les headers `Received:` est celle de Mailtrap. |
| Mail entrant (MX) | Émetteur tiers→Cloudflare MX →Email Routing → Boîte finale | Aucun serveur de messagerie public n'écoute sur l'hôte d'origine. |
Pour préserver l'anonymat de l'infrastructure d'origine sur un serveur multi-vhosts :
Les enregistrements suivants doivent être déployés dans la zone DNS Cloudflare du domaine :
| Type | Nom / Hôte | Valeur / Cible | Statut Proxy | Commentaire |
|---|---|---|---|---|
| CNAME | `@` (racine) | `target.votre-cluster.org` | Proxied (Orange) | Proxy HTTP Cloudflare actif |
| CNAME | `www` | `target.votre-cluster.org` | Proxied (Orange) | Proxy HTTP Cloudflare actif |
| CNAME | `mtxxxxxx` | `smtp.mailtrap.live` | DNS Only (Gris) | Validation du domaine Mailtrap |
| CNAME | `rwmt1._domainkey` | `rwmt1.dkim.smtp.mailtrap.live` | DNS Only (Gris) | Clé DKIM #1 Mailtrap |
| CNAME | `rwmt2._domainkey` | `rwmt2.dkim.smtp.mailtrap.live` | DNS Only (Gris) | Clé DKIM #2 Mailtrap (Rotation) |
| MX | `@` | `route1.mx.cloudflare.net` (Prio 13) | DNS Only (Gris) | Cloudflare Email Routing |
| MX | `@` | `route2.mx.cloudflare.net` (Prio 27) | DNS Only (Gris) | Cloudflare Email Routing |
| MX | `@` | `route3.mx.cloudflare.net` (Prio 94) | DNS Only (Gris) | Cloudflare Email Routing |
| TXT | `cfxxxxx-1._domainkey` | `v=DKIM1; k=rsa; p=…` | DNS Only (Gris) | Clé DKIM Cloudflare (flux entrant) |
| TXT | `_dmarc` | `v=DMARC1; p=none; rua=mailto:dmarc@…` | DNS Only (Gris) | Politique DMARC + rapports |
| TXT | `@` | `v=spf1 include:_spf.mx.cloudflare.net include:_spf.mailtrap.io ~all` | DNS Only (Gris) | Autorisations SPF combinées |
Les enregistrements techniques d'authentification (DKIM, SPF, CNAMEs de validation Mailtrap) doivent impérativement rester en DNS Only (Nuage Gris). L'activation du proxy Cloudflare sur ces entrées casserait la résolution DNS brute requise par les serveurs de messagerie.
Ce vhost intercepte toutes les requêtes directes par IP ou utilisant un nom de domaine inconnu/non configuré. Il présente un certificat auto-signé générique et coupe la connexion TCP sans émettre de réponse HTTP.
Génération du certificat factice :
user@host:~/ $ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/ssl/private/dummy.key \ -out /etc/ssl/certs/dummy.crt \ -subj "/CN=null"
Configuration du vhost par défaut :
# /etc/nginx/conf.d/00-default-blackhole.conf server { listen 80 default_server; listen [::]:80 default_server; listen 443 ssl default_server; listen [::]:443 ssl default_server; server_name _; ssl_certificate /etc/ssl/certs/dummy.crt; ssl_certificate_key /etc/ssl/private/dummy.key; # Coupe immédiatement la connexion TCP return 444; }
Pour que les logs et l'application reçoivent la véritable IP du client et non celle des relais Cloudflare :
# /etc/nginx/conf.d/cloudflare-realip.conf set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; # IPv6 set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; real_ip_header CF-Connecting-IP;
Télécharger le certificat CA public Cloudflare pour Authenticated Origin Pulls :
user@host:~/ $ curl -s https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem -o /etc/ssl/certs/cloudflare-origin-pull.crt
Configuration du Vhost sécurisé :
# /etc/nginx/sites-available/app.domain.tld.conf server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name domain.tld www.domain.tld; # Certificat Cloudflare Origin CA (valide 15 ans, non listé dans les CT logs) ssl_certificate /etc/ssl/certs/cloudflare_origin.crt; ssl_certificate_key /etc/ssl/private/cloudflare_origin.key; # Authenticated Origin Pulls (mTLS) : Rejette tout client qui n'est pas Cloudflare ssl_client_certificate /etc/ssl/certs/cloudflare-origin-pull.crt; ssl_verify_client on; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:8080; # Ou socket PHP-FPM / conteneur applicatif 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; } }
Pour router les notifications systèmes et applicatives sans divulguer l'infrastructure :
# Vérifier l'inclusion SPF user@host:~/somedir $ dig TXT domain.tld +short # Vérifier la résolution des clés DKIM Mailtrap user@host:~/somedir $ dig CNAME rwmt1._domainkey.domain.tld +short user@host:~/somedir $ dig CNAME rwmt2._domainkey.domain.tld +short # Vérifier la politique DMARC user@host:~/somedir $ dig TXT _dmarc.domain.tld +short
Exécuter un test depuis une machine externe vers l'IP publique directe :
# 1. Test direct par IP (Doit être fermé ou retourner HTTP 444 sans certificat exposé) user@host:~/somedir $ curl -k -v https://<IP_PUBLIQUE_SERVEUR>/ # 2. Test avec en-tête Host forgé (Doit échouer car le mTLS Cloudflare rejette la connexion) user@host:~/somedir $ curl -k -v -H "Host: domain.tld" https://<IP_PUBLIQUE_SERVEUR>/
*Résultat attendu :* La connexion TLS échoue immédiatement (rejet `ssl_verify_client`) ou retourne un statut vide `444`.
Une fois la période d'observation des rapports Mailtrap passée sans détection de rejets légitimes :