Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

Toolbox Dev — Ideo-Lab

IDEOLAB Mail Server — Guide complet d’installation Mailcow sur Hetzner

Guide opérationnel complet pour installer un serveur email souverain sur VPS Hetzner avec Mailcow Dockerized, DNS propres, SPF, DKIM, DMARC, PTR, sécurité, monitoring et backup.

Hetzner  •  Mailcow  •  DNS / SPF / DKIM / DMARC

Objectif du guide

Ce guide décrit l’installation complète d’un serveur email professionnel indépendant, basé sur un VPS Hetzner et Mailcow. Il couvre l’ensemble du parcours : création du VPS, déblocage SMTP, DNS, PTR, installation Docker, configuration Mailcow, tests Gmail, sécurité anti-blacklist, monitoring et sauvegardes.

Principe fondamental : un serveur mail ne se gagne pas seulement avec Postfix. Il se gagne avec un alignement propre entre IP, PTR, hostname, DNS, SPF, DKIM, DMARC, TLS, réputation IP, restrictions d’envoi et monitoring.

Stack retenue

  • Provider : Hetzner Cloud
  • OS : Ubuntu 24.04 LTS
  • Mail stack : Mailcow Dockerized
  • Webmail : SOGo
  • Anti-spam : Rspamd
  • SMTP/IMAP : Postfix + Dovecot

Résultat attendu

  • Réception Gmail vers guillaume@ideo-lab.com
  • Envoi Mailcow vers Gmail
  • SPF/DKIM/DMARC validés
  • PTR IPv4 propre
  • Rate limit SMTP actif
  • Fail2Ban / netfilter renforcé

Points critiques

  • Port 25 sortant ouvert par Hetzner
  • MX exclusif vers Mailcow
  • Suppression des anciens MX Zoho
  • Suppression des anciens SPF contradictoires
  • Désactivation IPv6 mail tant que le PTR IPv6 n’est pas parfait

Pré-requis

ÉlémentValeur utiliséeRôle
Domaineideo-lab.comDomaine mail principal
FQDN mailmail.ideo-lab.comNom officiel du serveur mail
IPv4 serveur159.69.80.91IP publique Hetzner
Provider DNSGoDaddy / zone DNS actuelleGestion A, MX, TXT
Provider VPSHetzner CloudServeur mail
Stopper immédiatement si :
  • le port 25 sortant n’est pas ouvert ;
  • le PTR IPv4 n’est pas configurable ;
  • le domaine ne peut pas être modifié côté DNS ;
  • un autre provider mail garde les MX actifs.

Ports nécessaires

PortUsageStatut
25SMTP entrant/sortant serveur à serveurObligatoire
80HTTP ACME / Let’s EncryptObligatoire
443HTTPS Mailcow / SOGoObligatoire
465SMTPS clientRecommandé
587SMTP submission client STARTTLSRecommandé
993IMAPS clientRecommandé

Création et préparation Hetzner

Créer le VPS

  • Projet : Default ou projet dédié Mail
  • Location : Nuremberg
  • Image : Ubuntu 24.04
  • Type : CPX / CX selon budget
  • Nom serveur conseillé : mail-ideo

Demander ouverture du port 25

Hello,

            Could you please unblock outbound SMTP port 25 for my server?

            Server IP: 159.69.80.91

            This server is used for a legitimate private mail server.
            SPF, DKIM, DMARC, PTR and abuse controls will be configured.

            Thank you.

Configurer le PTR IPv4

Dans Hetzner Console :

Servers
            -> server
            -> Networking
            -> Public Network
            -> IPv4
            -> Edit Reverse DNS
PTR attendu →159.69.80.91 -> mail.ideo-lab.com

Tester le port 25

telnet gmail-smtp-in.l.google.com 25
Attendu →220 gmail-smtp-in.l.google.com

DNS complets

Records obligatoires

TypeNomValeurTTL
Amail159.69.80.91600
MX@mail.ideo-lab.com priorité 10600
TXT@v=spf1 mx ip4:159.69.80.91 ~all600
TXT_dmarcv=DMARC1; p=none;600
TXTdkim._domainkeyv=DKIM1;k=rsa;...600
À supprimer lors de la migration :
  • anciens MX Zoho : mx.zoho.eu, mx2.zoho.eu, mx3.zoho.eu
  • anciens SPF contradictoires avec Zoho ou Mailersend
  • ancien DKIM Zoho si plus utilisé : zmail._domainkey

Vérifications DNS

nslookup -type=mx ideo-lab.com
            dig mail.ideo-lab.com
            dig -x 159.69.80.91
            dig TXT ideo-lab.com
            dig TXT _dmarc.ideo-lab.com
            dig TXT dkim._domainkey.ideo-lab.com
DMARC : pendant la migration, garder p=none. Après stabilisation, passer progressivement à p=quarantine, puis éventuellement p=reject.

Installation Linux / Docker

Hostname système

hostnamectl set-hostname mail.ideo-lab.com
            hostname
Attendu →mail.ideo-lab.com

Packages système

apt update
            apt install -y git curl jq dnsutils telnet nano mailutils docker.io docker-compose

Installer Docker Compose v2 si nécessaire

mkdir -p /usr/local/lib/docker/cli-plugins
            curl -SL https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
            chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
            docker compose version
Attendu →Docker Compose version v2.x

Installation Mailcow

Cloner Mailcow

cd /opt
            git clone https://github.com/mailcow/mailcow-dockerized
            cd /opt/mailcow-dockerized

Générer la configuration

./generate_config.sh
QuestionRéponse
FQDNmail.ideo-lab.com
TimezoneEurope/Madrid
Branch1 / stable
IPv6 DockerY si demandé, puis désactiver dans mailcow.conf ensuite si PTR IPv6 absent

Désactiver IPv6 mail si PTR IPv6 absent

nano /opt/mailcow-dockerized/mailcow.conf
ENABLE_IPV6=n

Démarrer Mailcow

docker compose pull
            docker compose up -d
            docker ps

Accès interface

InterfaceURLLogin
Admin Mailcowhttps://mail.ideo-lab.com/adminadmin
Webmail SOGohttps://mail.ideo-lab.com/SOGoguillaume@ideo-lab.com

Tests de validation

Test réception Gmail vers Mailcow

Envoyer depuis Gmail vers :

guillaume@ideo-lab.com

Observer les logs Postfix

cd /opt/mailcow-dockerized
            docker compose logs -f postfix-mailcow
Succès attendu →status=sent (250 2.0.0)

Test envoi Mailcow vers Gmail

  • Configurer Thunderbird si SOGo pose problème côté navigateur ancien.
  • IMAP : mail.ideo-lab.com, port 993, SSL/TLS.
  • SMTP : mail.ideo-lab.com, port 587, STARTTLS.
  • Utilisateur : guillaume@ideo-lab.com.

Lire les retours Gmail

Erreur GmailCauseCorrection
550-5.7.26 DMARCSPF/DKIM/DMARC incohérentsCorriger SPF, DKIM, DMARC p=none
550-5.7.25 PTRIPv6 sans reverse DNSDésactiver IPv6 ou configurer PTR IPv6
Unauthenticated emailDKIM absent ou SPF non propagéAjouter dkim._domainkey Mailcow

Sécurité anti-blacklist

Vérifier open relay

cd /opt/mailcow-dockerized
            docker compose exec postfix-mailcow postconf smtpd_recipient_restrictions
Contrôle critique →reject_unauth_destination

Fail2Ban / Netfilter Mailcow

ParamètreValeur recommandée
Durée du bannissement86400
Durée max. du bannissement604800
Nb max. de tentatives3
Fenêtre de nouvel essai900
IPv4 subnet ban/32
IPv6 subnet ban/128

Rate limit mailbox

Dans la boîte guillaume@ideo-lab.com, onglet Limite d’envoi :

Valeur →30 msgs / minute

2FA admin Mailcow

Activer OTP pour l’utilisateur admin :

Système
            -> Accès
            -> Authentification à deux facteurs
            -> OTP
Conseil : utiliser Aegis Authenticator plutôt qu’une application OTP inconnue pleine de publicités. Sauvegarder la clé secrète OTP ou les codes de secours dans un emplacement sécurisé.

Monitoring blacklist et alertes serveur

Script blacklist

mkdir -p /opt/mail-monitoring
            nano /opt/mail-monitoring/check_blacklist.sh
#!/bin/bash

            IP="159.69.80.91"
            EMAIL="guillaume@ideo-lab.com"

            REVERSED_IP=$(echo $IP | awk -F. '{print $4"."$3"."$2"."$1}')

            BLACKLISTS=(
            "zen.spamhaus.org"
            "bl.spamcop.net"
            "b.barracudacentral.org"
            "dnsbl-1.uceprotect.net"
            )

            ALERT=""

            for BL in "${BLACKLISTS[@]}"
            do
            RESULT=$(dig +short ${REVERSED_IP}.${BL})

            if [ ! -z "$RESULT" ]; then
            ALERT="${ALERT}\nBLACKLIST DETECTED : $BL ($RESULT)"
            fi
            done

            if [ ! -z "$ALERT" ]; then
            echo -e "$ALERT" | mail -s "MAIL SERVER BLACKLIST ALERT" $EMAIL
            fi
chmod +x /opt/mail-monitoring/check_blacklist.sh
            /opt/mail-monitoring/check_blacklist.sh

Script alertes serveur

nano /opt/mail-monitoring/server_alerts.sh
#!/bin/bash

            EMAIL="guillaume@ideo-lab.com"

            ALERT=""

            DISK=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')

            if [ "$DISK" -gt 85 ]; then
            ALERT="${ALERT}\n[DISK] Usage critique : ${DISK}%"
            fi

            RAM=$(free | grep Mem | awk '{printf("%.0f"), $3/$2 * 100.0}')

            if [ "$RAM" -gt 90 ]; then
            ALERT="${ALERT}\n[RAM] Usage critique : ${RAM}%"
            fi

            CONTAINERS=(
            "mailcowdockerized-postfix-mailcow-1"
            "mailcowdockerized-dovecot-mailcow-1"
            "mailcowdockerized-nginx-mailcow-1"
            )

            for C in "${CONTAINERS[@]}"
            do
            STATUS=$(docker inspect -f '' $C 2>/dev/null)

            if [ "$STATUS" != "true" ]; then
            ALERT="${ALERT}\n[CONTAINER DOWN] $C"
            fi
            done

            QUEUE=$(docker exec mailcowdockerized-postfix-mailcow-1 postqueue -p | tail -1 | awk '{print $5}')

            if [[ "$QUEUE" =~ ^[0-9]+$ ]]; then
            if [ "$QUEUE" -gt 50 ]; then
            ALERT="${ALERT}\n[SMTP QUEUE] Queue importante : $QUEUE messages"
            fi
            fi

            if [ ! -z "$ALERT" ]; then
            echo -e "$ALERT" | mail -s "MAIL SERVER ALERT" $EMAIL
            fi

Cron monitoring

chmod +x /opt/mail-monitoring/server_alerts.sh
            crontab -e
0 6 * * * /opt/mail-monitoring/check_blacklist.sh
            */15 * * * * /opt/mail-monitoring/server_alerts.sh

Backup Mailcow

Volumes Docker Mailcow

docker volume ls | grep mailcow

Volumes essentiels :

  • mailcowdockerized_vmail-vol-1 : emails
  • mailcowdockerized_mysql-vol-1 : configuration, domaines, comptes
  • mailcowdockerized_redis-vol-1 : cache / sessions
  • mailcowdockerized_crypt-vol-1 : données cryptographiques

Script backup

mkdir -p /backup/mailcow
            nano /backup/mailcow/backup_mailcow.sh
#!/bin/bash

            DATE=$(date +%Y-%m-%d_%H-%M-%S)
            BACKUP_DIR="/backup/mailcow/$DATE"

            mkdir -p "$BACKUP_DIR"

            cd /opt/mailcow-dockerized || exit 1

            docker compose stop postfix-mailcow dovecot-mailcow

            docker run --rm -v mailcowdockerized_vmail-vol-1:/volume -v "$BACKUP_DIR":/backup alpine tar czf /backup/vmail.tar.gz -C /volume .
            docker run --rm -v mailcowdockerized_mysql-vol-1:/volume -v "$BACKUP_DIR":/backup alpine tar czf /backup/mysql.tar.gz -C /volume .
            docker run --rm -v mailcowdockerized_redis-vol-1:/volume -v "$BACKUP_DIR":/backup alpine tar czf /backup/redis.tar.gz -C /volume .
            docker run --rm -v mailcowdockerized_crypt-vol-1:/volume -v "$BACKUP_DIR":/backup alpine tar czf /backup/crypt.tar.gz -C /volume .

            cp mailcow.conf "$BACKUP_DIR/"
            cp docker-compose.yml "$BACKUP_DIR/"

            docker compose start postfix-mailcow dovecot-mailcow

            find /backup/mailcow/* -maxdepth 0 -type d -mtime +7 -exec rm -rf {} \;

            echo "Backup terminé : $BACKUP_DIR"
chmod +x /backup/mailcow/backup_mailcow.sh
            /backup/mailcow/backup_mailcow.sh
            ls -lh /backup/mailcow/YYYY-MM-DD_HH-MM-SS
Attendu →vmail.tar.gz, mysql.tar.gz, redis.tar.gz, crypt.tar.gz
Limite : un backup local sur le même serveur ne protège pas contre la perte du VPS. Une copie externe doit être ajoutée ensuite : Hetzner Storage Box, S3, rsync vers autre VPS, ou backup chiffré distant.

Exploitation quotidienne

Commandes utiles

ActionCommande
Voir containersdocker ps
Voir logs Postfixdocker compose logs -f postfix-mailcow
Voir logs Dovecotdocker compose logs -f dovecot-mailcow
Redémarrer Mailcowdocker compose down && docker compose up -d
Mettre à jour imagesdocker compose pull && docker compose up -d
Voir queue SMTPdocker exec mailcowdockerized-postfix-mailcow-1 postqueue -p

Warm-up IP

Règle : ne pas envoyer de volume massif au début. Pendant 2 à 3 semaines, rester sur des emails humains, faibles volumes, conversations réelles, réponses naturelles.
PériodeVolume conseilléObjectif
Semaine 15 à 20 emails/jourCréer une réputation initiale propre
Semaine 220 à 50 emails/jourStabiliser Gmail/Outlook
Semaine 3+ProgressifNe jamais faire de bulk marketing sans stratégie dédiée

Dépannage

Gmail refuse avec DMARC

550-5.7.26 Unauthenticated email from domain is not accepted due to DMARC policy
  • Vérifier SPF : v=spf1 mx ip4:159.69.80.91 ~all
  • Vérifier DKIM : dkim._domainkey
  • Mettre DMARC temporairement en p=none
  • Supprimer les anciens SPF Zoho/Mailersend

Gmail refuse avec PTR IPv6

550-5.7.25 IP address sending this message does not have a PTR record setup
  • Identifier l’IP mentionnée par Gmail.
  • Si c’est IPv6, désactiver ENABLE_IPV6=n dans Mailcow.
  • Redémarrer Mailcow.

Mail reçu dans les logs mais pas visible

  • Recharger SOGo avec CTRL+F5.
  • Vérifier Pourriel / Archive.
  • Vérifier logs Dovecot.

SOGo affiche Unauthorized

cd /opt/mailcow-dockerized
            docker compose restart sogo-mailcow nginx-mailcow

Docker Compose ancien

docker-compose --version
            docker compose version

Mailcow moderne nécessite Docker Compose v2. Installer le plugin CLI si nécessaire.

Erreur backup : Cannot stat data/vmail

La version courante de Mailcow stocke les emails dans un volume Docker, pas dans data/vmail.

docker volume ls | grep mailcow