Mailcow su VPS: email self-hosted accettata da Gmail
Verifica la porta 25 prima di installare mailcow: un blocco lascia i messaggi in coda fino al rimbalzo. Configura il DNS per evitare l'errore Gmail 550 5.7.25 sul PTR.
Cosa stai realizzando
Un server di posta completo su un server di tua proprietà: SMTP per inviare e ricevere messaggi, IMAP per mantenere sincronizzati telefono e laptop, un client webmail e un filtro antispam che assegna un punteggio a ogni messaggio in entrambe le direzioni. mailcow-dockerized include Postfix, Dovecot, Rspamd, la webmail SOGo, MariaDB, Redis e un client ACME in un unico stack Docker Compose, quindi il software non è l’aspetto più complesso. Lo avrai in esecuzione in mezz’ora.
La parte complessa è tutto ciò che lo circonda. La posta elettronica è l’unico servizio per cui il resto di Internet diffida attivamente dei server appena configurati, e la differenza tra «funziona» e «Gmail elimina silenziosamente ogni messaggio» dipende soprattutto da quattro record DNS e da un’impostazione relativa alla reputazione dell’indirizzo IP che potresti non controllare completamente. Leggi i prerequisiti riportati di seguito prima di noleggiare qualsiasi risorsa. Se, dopo averli letti, decidi che il lavoro necessario per costruire una reputazione non vale la pena, è una scelta legittima: la nostra panoramica di ciò che vale davvero la pena gestire in self-hosting nel 2026 colloca la posta elettronica nella categoria «solo se fai sul serio» proprio per questi motivi.
I prerequisiti sono il progetto
Se ne manca anche solo uno, invierai messaggi che non arriveranno mai. Di seguito sono elencati, in ordine approssimativo di frequenza con cui causano problemi:
La porta in uscita 25 deve essere aperta. Il server consegna i messaggi a Gmail e Microsoft tramite TCP sulla porta 25. Molti provider VPS e cloud bloccano per impostazione predefinita le connessioni in uscita sulla porta 25 per contrastare lo spam. Il blocco è silenzioso: all'avvio non viene segnalato alcun errore, tutto sembra funzionare e i messaggi restano in stato deferred nella coda finché Postfix rinuncia e li restituisce al mittente (mailcow imposta a cinque giorni il tempo massimo di permanenza in coda). Esegui il test prima di installare qualsiasi componente. Se la porta è bloccata, l'unica soluzione è aprire un ticket al supporto e chiedere al provider di abilitarla. Alcuni provider lo fanno per gli account più datati, altri non lo consentono mai.
Un indirizzo IP pulito, con una reputazione utilizzabile. Gli indirizzi IP VPS riciclati sono spesso già presenti nelle blocklist a causa dello spam inviato dal precedente assegnatario. Verifica il tuo indirizzo con un servizio come la ricerca Spamhaus o mxtoolbox prima di procedere. Un IP inserito in una blocklist comporta rifiuti che non puoi risolvere modificando il codice.
Il controllo del DNS e un record PTR corretto. Devi poter aggiungere record alla zona del tuo dominio e devi configurare il DNS inverso (PTR) dell'indirizzo IP del server, facendolo puntare al nome host della posta. Il PTR è quasi mai configurato nel pannello DNS: viene gestito dal titolare dell'indirizzo IP. Per questo si imposta nel pannello di controllo del provider VPS oppure tramite ticket.
6 GiB di RAM e 2 vCPU rappresentano il livello minimo consigliato. Il requisito minimo indicato da mailcow per un'installazione privata è 6 GiB di RAM più 1 GiB di swap; quando il servizio viene utilizzato da alcuni utenti, sono consigliati 8 GiB. Al di sotto di circa 2.5 GiB, generate_config.sh propone di disabilitare lo scanner antivirus ClamAV per evitare che il kernel inizi a terminare i container. Il requisito minimo di spazio su disco documentato da mailcow è 20 GiB prima di memorizzare qualsiasi messaggio. Per iniziare, quindi, assegna più spazio e lascia margine per la crescita delle mailbox.
Un nome DNS, non un semplice indirizzo IP. Scegli un nome host come mail.example.com. Questo nome diventa il tuo MAILCOW_HOSTNAME, il subject del certificato TLS, la destinazione del PTR e il banner SMTP. Usalo in modo coerente ovunque.
Passaggio 1: verificare che la porta in uscita 25 sia aperta
Esegui prima questo controllo. Se fallisce, tutto il resto è inutile. Dal VPS appena creato, prova ad avviare una conversazione SMTP con un server di posta reale:
sudo apt update && sudo apt install -y netcat-openbsd
nc -vz -w 5 gmail-smtp-in.l.google.com 25Un risultato corretto è immediato:
Connection to gmail-smtp-in.l.google.com (142.250.x.x) 25 port [tcp/smtp] succeeded!Se la porta è bloccata, il comando attende tutti i cinque secondi e poi restituisce un errore:
nc: connect to gmail-smtp-in.l.google.com port 25 (tcp) timed out: Operation now in progressSu un VPS appena creato, senza regole firewall in uscita configurate da te, il timeout indica un blocco applicato dal provider. Si tratta di un filtro di rete lato provider, non del tuo firewall; nessuna modifica locale può risolvere il problema. Apri un ticket con questo testo: "Please enable outbound TCP port 25 for my VPS at <IP>; I am running a legitimate mail server." Non installare mailcow finché il comando non restituisce "succeeded". La porta 25 in ingresso, usata dagli altri server per raggiungere il tuo, segue un percorso distinto ed è generalmente aperta. Il limite applicato dai provider riguarda il traffico in uscita.
Passaggio 2: configurare ora i record DNS
Le modifiche DNS richiedono tempo per propagarsi, quindi pubblica tutti i record possibili prima dell'installazione. Supponiamo che il tuo dominio sia example.com, che il tuo host di posta sia mail.example.com e che l'indirizzo IP sia 10.0.0.10. Nella tua zona DNS, crea:
mail.example.com. A 10.0.0.10
mail.example.com. AAAA 2001:db8::10 ; only if you have IPv6
example.com. MX 10 mail.example.com.
example.com. TXT "v=spf1 mx -all"
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:postmaster@example.com"Il record SPF indica che «solo il mio MX può inviare messaggi per questo dominio; tutti gli altri devono essere rifiutati». Imposta DMARC su p=none per iniziare, così puoi monitorare i report senza rifiutare i tuoi messaggi; passa a p=quarantine e poi a p=reject quando avrai verificato l'allineamento. Mancano volutamente due record: DKIM, che mailcow genera per te nel passaggio 6, e PTR, che devi configurare ora nel pannello del provider.
Imposta il PTR (DNS inverso) di 10.0.0.10 su mail.example.com, il valore esatto di MAILCOW_HOSTNAME. Questo è il record che viene dimenticato più spesso e i principali provider rifiutano i messaggi quando manca o è errato. Se il pannello non dispone di un campo rDNS, apri un ticket.
Passaggio 3: installare Docker
mailcow richiede Docker Engine 24.0 o versione successiva e Docker Compose 2.0 o versione successiva. Il plugin Compose (docker compose) è il metodo più semplice, ma mailcow supporta anche il binario autonomo docker-compose. Usate lo script ufficiale di Docker invece del pacchetto docker.io di Ubuntu, che non include alcun plugin Compose:
curl -fsSL https://get.docker.com | sudo sh
sudo docker compose versionDovreste vedere una riga Docker Compose version che indica v2.x o v5.x. Compose v5, rilasciato nel 2025, è funzionalmente identico alla versione 2 ed entrambe le versioni soddisfano i requisiti di mailcow. Se docker compose version restituisce docker: 'compose' is not a docker command, Docker Engine è installato ma il plugin Compose non lo è. Installate il plugin dal repository di Docker, eseguite nuovamente lo script precedente oppure consultate la nostra guida introduttiva a Docker Compose, che configura entrambi dal repository apt di Docker.
Passaggio 4: clonare mailcow e generare la configurazione
cd /opt
sudo git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
umask
sudo ./generate_config.shVerificare che umask restituisca 0022 è in linea con la procedura di installazione di mailcow, che imposta umask 0022 prima della clonazione. mailcow non verifica questo valore e una shell root appena aperta su Ubuntu 24.04 restituisce già 0022. Lo script chiede quindi l'unico dato realmente importante: il nome host completo. Inserire mail.example.com; il valore deve corrispondere esattamente al record A e al record PTR. Lo script scrive mailcow.conf, l'unico file di ambiente letto dall'intero stack. Aprirlo se è necessario modificare le porte web (HTTP_PORT, HTTPS_PORT) o disabilitare ClamAV su un server con poca memoria:
MAILCOW_HOSTNAME=mail.example.com
HTTP_PORT=80
HTTPS_PORT=443
SKIP_CLAMD=n # set to y to drop the virus scanner on a <2.5 GiB boxSKIP_FTS=y è l'altra impostazione rilevante su un server con poca RAM: la ricerca full-text è il secondo processo che consuma più memoria tra quelli indicati nella documentazione di mailcow e disabilitarla comporta soltanto la perdita della ricerca nel testo dei messaggi dalla webmail.
Lasciare HTTP_PORT=80 e HTTPS_PORT=443 ai valori predefiniti, a meno che sullo stesso host non siano già utilizzati da un altro servizio. Il client ACME integrato di mailcow usa per impostazione predefinita la challenge HTTP-01, che richiede che la porta 80 sia raggiungibile da Internet per ottenere il certificato. (A partire da mailcow 2026-03 è possibile usare anche una challenge DNS-01 con ACME_DNS_CHALLENGE=y. Questa modalità non richiede la porta 80, ma richiede l'accesso API al provider DNS.) Per questo la configurazione semplice non esegue una configurazione separata con nginx e Certbot sullo stesso server: mailcow emette e rinnova internamente i propri certificati TLS e un secondo servizio in ascolto sulle porte 80/443 lo impedirebbe. mailcow documenta anche l'alternativa: associare localmente i listener web con HTTP_BIND=127.0.0.1 HTTP_PORT=8080 HTTPS_BIND=127.0.0.1 HTTPS_PORT=8443, installare un reverse proxy sulle porte 80 e 443 e, se si desidera affidare i certificati a un'istanza locale di Certbot, indicare a mailcow di usarli e aggiungere un hook per il rinnovo. Su un server di posta questo comporta più componenti da gestire. Lo stesso compromesso vale quindi per qualsiasi altro servizio che richieda un front end web pubblico: una configurazione come Halcyon che trasforma una libreria Jellyfin in un videonoleggio degli anni 90, che richiede un proprio reverse proxy sulla porta 443, dovrebbe essere eseguita su un altro host, a meno di spostare mailcow dietro quel proxy. Fa eccezione un servizio che non richiede mai una porta web pubblica: un relay RustDesk self-hosted ascolta soltanto sull'intervallo di porte 21115-21119 e può quindi condividere il server, purché resti disponibile una quantità sufficiente di banda.
Passaggio 5: avviare lo stack e accedere
sudo docker compose pull
sudo docker compose up -d
sudo docker compose psIl comando pull scarica circa due dozzine di immagini; attendere alcuni minuti. Quando docker compose ps mostra tutti i container nello stato running (oppure healthy), aprire https://mail.example.com/admin in un browser. Le credenziali amministrative predefinite sono il nome utente admin e la password moohoo. Cambiare immediatamente la password nell'interfaccia di amministrazione, alla voce System → Configuration, quindi Access → Administrators. Se il browser mostra l'avviso NET::ERR_CERT_AUTHORITY_INVALID, il certificato ACME non è ancora stato emesso. Consultare la sezione dedicata agli errori ACME prima di considerare il problema un malfunzionamento: nei primi uno o due minuti è normale che venga utilizzato un certificato autofirmato provvisorio.
Passaggio 6: aggiungere un dominio e una casella di posta e pubblicare DKIM
Nell'interfaccia di amministrazione aprire E-Mail → Configuration. Nella scheda Domains fare clic su Add domain e inserire example.com. Quindi, nella sezione Mailboxes, fare clic su Add mailbox per creare you@example.com con una password. A questo punto è già disponibile una casella funzionante, raggiungibile tramite IMAP.
Ora creare la chiave DKIM. Accedere a System → Configuration, quindi a Options → ARC/DKIM keys. mailcow potrebbe avere già generato una chiave quando è stato aggiunto il dominio. In caso contrario, generarla nella stessa sezione, selezionare il dominio, mantenere il selettore dkim, scegliere 2048-bit e fare clic su Add. Copiare il lungo valore TXT visualizzato e pubblicarlo come segue:
dkim._domainkey.example.com. TXT "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqh...long-key...QAB"La pagina Domains di mailcow contiene un pulsante DNS che elenca tutti i record previsti e mostra un segno di spunta verde o una croce rossa in base a ciò che è effettivamente pubblicato. Usarlo come checklist e verificare che ogni riga sia verde prima di testare la recapitalità. Una riga DKIM rossa dopo la pubblicazione indica generalmente che la chiave è stata suddivisa in modo errato tra più parti TXT. Una chiave a 2048 bit supera il limite di 255 caratteri previsto per una singola stringa TXT. Incollarla come un unico valore logico e lasciare che il provider DNS la suddivida automaticamente in più parti.
Passaggio 7: verifica della deliverability e raggiungi 10/10
Vai su mail-tester.com, copia l'indirizzo casuale visualizzato e inviagli un messaggio dalla nuova casella. Accedi a SOGo webmail all'indirizzo https://mail.example.com/SOGo e invialo da lì. Quindi fai clic su "Then check your score".
Punta a 10/10. Le detrazioni più comuni e le relative cause sono:
- SPF non allineato: il record
MX/SPF è assente oppure l'IP di invio non è incluso. Verifica nuovamente il record SPF TXT. - La firma DKIM non viene verificata: il record TXT
dkim._domainkeyè assente, la propagazione non è ancora completata oppure il contenuto è alterato. È l'errore più comune. - PTR assente o non corrispondente: il DNS inverso non risolve in
mail.example.com. Correggilo presso il provider. - IP presente in una blocklist: la reputazione precedente dell'IP. Richiedi la rimozione dalla lista oppure chiedi un IP con una reputazione migliore.
Non inviare messaggi reali a Gmail o Outlook finché il punteggio non è 10/10. Un punteggio basso associato a un IP nuovo può far contrassegnare il tuo dominio già dal primo giorno.
Passaggio 8: connettere un client di posta reale
Configura Thunderbird, Apple Mail o il telefono con questi parametri. Per tutti, il server è mail.example.com:
- IMAP: porta 993, SSL/TLS (oppure 143 con STARTTLS)
- Invio SMTP: porta 465, SSL/TLS (oppure 587 con STARTTLS)
- Nome utente: l'indirizzo completo,
you@example.com - Password: la password della casella configurata
Non inviare mai la posta del client tramite la porta 25. Questa porta è riservata alle comunicazioni tra server. mailcow non offre l'invio autenticato su questa porta e il client che la utilizza viene rifiutato. Se il client segnala Relay access denied, sta tentando di inviare tramite la porta 25 oppure senza autenticazione. Configuralo sulla porta 465 o 587 usando le credenziali della casella.
Passaggio 9: eseguire il backup di ciò che conta davvero
mailcow include uno script di backup che crea snapshot di ogni volume con stato persistente. Eseguilo su un disco esterno o su una destinazione remota montata:
sudo MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
./helper-scripts/backup_and_restore.sh backup allall acquisisce sei elementi. La perdita di uno qualsiasi di questi elementi comporta una perdita di dati: vmail (le caselle di posta effettive), crypt (le chiavi che decrittano vmail, inutilizzabili senza di esso), mysql (il database MariaDB che contiene domini, utenti, alias e impostazioni), redis (lo stato delle code e della cache), rspamd (i dati appresi per classificare spam e posta legittima) e postfix (la coda di posta). Lo script viene eseguito all'interno di un container di supporto e scrive archivi compressi, quindi i backup restano coerenti anche mentre lo stack è in esecuzione. Automatizzalo con un cron job notturno e aggiungi --delete-days 14 per eliminare i set meno recenti. Un cron job che smette di funzionare fallisce senza produrre notifiche. Invia quindi lo stato di uscita dello script a un sistema che controlli davvero; un server ntfy self-hosted richiede un comando curl su una sola riga nella stessa voce cron e invia l'errore al telefono la notte in cui si verifica. Il ripristino usa lo stesso script con restore. Questo comando elenca gli snapshot e consente di scegliere quali ripristinare. Un backup mai sottoposto a un test di ripristino è solo un'ipotesi, non un backup. Esegui almeno una prova a vuoto su una VPS di test.
Passaggio 10, Aggiornamenti pianificati
mailcow si aggiorna tramite il proprio script, che scarica il nuovo codice, esegue la migrazione di mailcow.conf, precarica le immagini e riavvia i container nell’ordine corretto:
cd /opt/mailcow-dockerized
sudo ./update.sh --check # reports whether an update exists, changes nothing
sudo ./update.sh # applies itEsegui prima il backup (Passaggio 9), perché una migrazione dello schema è difficile da annullare. Gli aggiornamenti vengono pubblicati frequentemente e includono correzioni di sicurezza per i demoni esposti a Internet. Non lasciare quindi un server di posta senza aggiornamenti per mesi. Se un aggiornamento lascia un container in stato non integro, sudo docker compose logs --tail=50 <service>-mailcow indica il demone che non è tornato operativo.
Una nota sull’hardening
mailcow esegue un proprio servizio netfilter (netfilter-mailcow) che blocca gli indirizzi IP dai quali provengono numerosi tentativi di accesso sulle porte della posta e della webmail. Il livello di protezione della posta è quindi già configurato. Questo non protegge SSH sull’host stesso, che rimane esposto e continua a ricevere attacchi brute-force. Abbina questa configurazione a Fail2ban che monitora il log di autenticazione SSH e all’accesso consentito solo tramite chiavi. Proteggi l’interfaccia di amministrazione di mailcow con una password robusta e, idealmente, non esporla a Internet oppure rendila accessibile tramite una VPN.
Modalità di errore, con le stringhe esatte
Le code di posta aumentano e i messaggi non vengono mai consegnati. Eseguire sudo docker compose exec postfix-mailcow postqueue -p oppure consultare la coda della posta nell'interfaccia di amministrazione; le voci restano in stato deferred per un massimo di cinque giorni, prima che Postfix le rifiuti definitivamente, con:
status=deferred (connect to gmail-smtp-in.l.google.com[142.250.x.x]:25: Connection timed out)Nella quasi totalità dei casi, la causa è il blocco della porta in uscita 25 da parte del provider (passaggio 1). Una regola in uscita nel firewall del proprio host produce lo stesso timeout, quindi va esclusa per prima; se l'host non presenta problemi, nessuna modifica alla configurazione può risolvere il problema: aprire un ticket. Non è un problema di DNS né di TLS; l'indicatore è la parola timed out associata a un server MX remoto sulla porta 25.
Gmail contrassegna tutti i messaggi come spam oppure li rifiuta. Aprire il messaggio in Gmail, selezionare "Mostra originale" e leggere i risultati dell'autenticazione. dkim=fail o dkim=none indica che il record TXT dkim._domainkey è assente, alterato o non ancora propagato; pubblicare nuovamente esattamente il contenuto mostrato dalla pagina ARC/DKIM e attendere la scadenza del TTL. spf=fail indica che i record SPF/MX non includono il proprio indirizzo IP. L'allineamento è essenziale: un solo controllo non superato è sufficiente per fare finire il messaggio nello spam.
I grandi provider rifiutano la connessione. I messaggi di mancata consegna o i log di Postfix contengono il rifiuto PTR di Gmail:
550-5.7.25 [10.0.0.10] The IP address sending this message does not have a PTR
550-5.7.25 record setup, or the corresponding forward DNS entry does not match
550 5.7.25 the sending IP. As a policy, Gmail does not accept messages from IPs
550 5.7.25 with missing PTR records.Il codice 550 5.7.25 indica che il reverse DNS è assente o non corrisponde. Impostare presso il provider il PTR del proprio IP su mail.example.com (passaggio 2). Il record forward (A) e quello reverse (PTR) devono corrispondere e devono indicare lo stesso host con cui mailcow si presenta agli altri server.
Il browser mostra un avviso relativo al certificato che non scompare. Il container acme-mailcow non è riuscito a ottenere un certificato valido. Controllarne il log:
sudo docker compose logs acme-mailcow | tail -n 40Una riga come Cannot validate any hostnames, skipping Let's Encrypt for 1 hour. o un errore nella challenge indica che la porta 80 non è raggiungibile da Internet oppure che il record A non punta a questo server. Se è stato pubblicato un record AAAA, controllare anche quello: mailcow esegue prima la validazione tramite il record AAAA e salta il controllo A quando il record è presente. Di conseguenza, un record AAAA errato o un percorso IPv6 che non raggiunge la porta 80 causa il fallimento della validazione anche se il record A è corretto. Verificare che mail.example.com risolva verso il server, consentire il traffico sulle porte 80 e 443 attraverso qualsiasi firewall dell'host e assicurarsi che nessun altro processo sia in ascolto su queste porte. Dopo avere corretto la causa, riavviare il client con sudo docker compose restart acme-mailcow invece di attendere il termine del back-off di un'ora.
FAQ
Il self-hosting della posta elettronica conviene davvero?
Se vuoi mantenere il controllo sui dati, usare alias illimitati e avere il pieno controllo, sì: mailcow offre uno stack professionale al costo di un VPS. Tuttavia, la deliverability richiede un’attività costante: reputazione dell’IP, allineamento DNS e monitoraggio delle blocklist non finiscono mai del tutto. Per un indirizzo aziendale mission-critical, quando un solo giorno nella cartella spam di qualcuno può costarti caro, un provider gestito è la scelta più pragmatica. Scegli il self-hosting quando dai più valore al controllo che alla comodità e sei disposto a gestirlo regolarmente. Se il tuo obiettivo reale è tenere i dati lontani dai server di terzi, più che gestire la posta in autonomia, inizia da un servizio su cui nessun altro può intervenire: una libreria fotografica non ha alcun problema di deliverability e il nostro confronto tra PhotoPrism e Immich illustra i requisiti minimi di RAM e i comandi di backup per eseguire questa soluzione sullo stesso tipo di VPS.
Come posso sapere se la porta in uscita 25 è bloccata?
Esegui nc -vz -w 5 gmail-smtp-in.l.google.com 25 dal server. Il messaggio "succeeded!" indica che la porta è aperta; un timed out dopo la pausa indica che la porta è bloccata e, se il server non ha regole firewall in uscita configurate da te, il blocco dipende dal provider. È la causa più comune per cui un server self-hosted riesce a ricevere la posta ma non a inviarla. L’unica soluzione a un blocco del provider è che il provider apra la porta: nessuna impostazione locale può modificarlo.
Perché la mia posta finisce ancora nella cartella spam di Gmail?
Quasi sempre a causa di una catena di autenticazione non corretta. In Gmail usa "Show original" e cerca spf=pass, dkim=pass e dmarc=pass. Un dkim=fail indica un record TXT dkim._domainkey mancante o alterato; anche una mancata corrispondenza del PTR o un nuovo IP senza cronologia di invio penalizzano la reputazione. Prima porta mail-tester.com a 10/10, poi aumenta gradualmente la reputazione dell’IP, inviando pochi messaggi al giorno e incrementando il volume, invece di inviare subito una grande quantità di posta.
Che cosa devo includere esattamente nel backup?
Esegui backup_and_restore.sh backup all e conserva l’intero insieme di dati fuori dal server. Include vmail, cioè le caselle di posta, crypt, cioè le chiavi che le decrittografano, il database MariaDB con domini, utenti, alias e impostazioni, Redis, i dati appresi da Rspamd e la coda di Postfix. Il volume crypt è quello che viene trascurato più spesso: senza di esso, il backup di vmail contiene solo testo cifrato illeggibile. Verifica almeno una volta il ripristino su un server di prova.
Posso eseguire mailcow su un VPS da 2 GB?
Non in modo affidabile. generate_config.sh propone di disabilitare ClamAV sotto circa 2.5 GiB e, anche con ClamAV disabilitato, Rspamd, Dovecot, MariaDB e il resto dello stack si contendono la memoria. Con un carico reale finirai quindi per usare lo swap e incorrere in terminazioni OOM. Considera 6 GiB più 1 GiB di swap come requisito minimo per un’installazione stabile per un singolo utente e passa a 8 GiB non appena il servizio viene utilizzato da più di un paio di persone.