Ovvero: come far parlare stampanti, AS/400 e altri dinosauri con il cloud, senza impazzire.

Indice
- Il problema (e perché non puoi ignorarlo)
- L’architettura della soluzione
- Prerequisiti
- Setup del server Postfix
- Configurazione del connettore M365
- Test e verifica
- Il tool mail-mini: diagnostica per esseri umani
- Manutenzione ordinaria
- Troubleshooting
- Considerazioni finali
1. Il problema (e perché non puoi ignorarlo)
Hai migrato la posta su Microsoft 365. Complimenti, benvenuto nel 2015. Ma ora hai un problema: tutti quei dispositivi e applicativi che mandavano mail allegramente verso il vecchio server SMTP interno non sanno cosa sia OAuth2, non supportano TLS 1.2, e alcuni non sanno nemmeno cosa sia una password.
Parlo di:
- Stampanti multifunzione che scansionano e inviano via mail (scan-to-email)
- Sistemi legacy tipo AS/400, ERP , gestionali con “SMTP server” come unico campo configurabile
- Script e cron job su server Linux che usano
sendmailomail - Applicazioni interne che non verranno mai aggiornate perché “funziona, non toccarlo”
- Dispositivi IoT e apparati di rete che mandano alert via mail
La soluzione Microsoft ufficiale? “Configura l’autenticazione SMTP con credenziali dedicate per ogni device”. Certo, e poi configuro anche 47 stampanti con username e password, pregando che nessuno cambi mai le credenziali. No grazie.
La soluzione intelligente: un relay SMTP interno che accetti mail dalla LAN senza autenticazione e le inoltri a M365 tramite un connettore dedicato.
2. L’architettura della soluzione
┌─────────────────────────────────────────────────────────────────────────┐
│ RETE INTERNA │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Stampante│ │ AS/400 │ │ Script │ │ ERP │ │
│ │ │ │ │ │ Linux │ │ Legacy │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬──────┴─────────────┘ │
│ │ │
│ ▼ porta 25 │
│ ┌─────────────────┐ │
│ │ SVR-SMTPRELAY │ │
│ │ (Postfix) │ │
│ │ 172.10.253.84 │ │
│ └────────┬────────┘ │
│ │ │
└────────────────────────────┼────────────────────────────────────────────┘
│
│ TLS sulla 25 (relay autenticato via IP)
▼
┌──────────────────────────────────┐
│ Microsoft 365 / Exchange │
│ nomedeltenat-com.mail. │
│ protection.outlook.com │
└──────────────────────────────────┘
Il flusso è semplice:
- I device interni mandano mail al relay (porta 25, no auth, no TLS obbligatorio)
- Postfix verifica che il mittente sia in una subnet autorizzata (
mynetworks) - Postfix inoltra a M365 con TLS obbligatorio
- M365 accetta perché l’IP pubblico del relay è in whitelist nel connettore
Vantaggi:
- Configurazione device: un solo campo (IP del relay)
- Nessuna credenziale da gestire sui device
- Logging centralizzato
- Possibilità di fare troubleshooting senza accedere a 47 stampanti diverse
3. Prerequisiti
Lato infrastruttura
- Una VM Linux (Ubuntu 24.04 LTS consigliato) con:
- IP statico sulla LAN
- Accesso in uscita verso internet (porta 25/TCP verso i server Microsoft)
- Almeno 1GB RAM, 10GB disco (Postfix è leggerissimo)
- L’IP pubblico attraverso cui il relay esce verso internet (per la whitelist M365)
Lato Microsoft 365
- Accesso admin a Exchange Online
- Un dominio verificato e accettato (nel nostro caso
nomedelclinete.com) - La possibilità di creare connettori inbound
Lato DNS (opzionale ma consigliato)
- Record SPF che includa l’IP pubblico del relay
- Un record A o CNAME per il relay (es.
smtp-relay.tuodominio.com)
4. Setup del server Postfix
4.1 Installazione
Su Ubuntu 24.04:
sudo apt update
sudo apt install postfix -y
Durante l’installazione, seleziona “Internet Site” e inserisci il nome dominio (es. nomedelclinete.com).
Se hai già installato Postfix o vuoi riconfigurare:
sudo dpkg-reconfigure postfix
4.2 Configurazione main.cf
Ecco il cuore della configurazione. Sostituisci il contenuto di /etc/postfix/main.cf:
sudo nano /etc/postfix/main.cf
# ---------------------------
# POSTFIX RELAY → M365
# ---------------------------
# Identità server
myhostname = svr-smtprelay.tuodominio.com
myorigin = tuodominio.com
mydestination = localhost
inet_interfaces = all
inet_protocols = ipv4
# LAN autorizzate (CIDR) - aggiungi qui le tue subnet
mynetworks = 127.0.0.0/8, 192.168.1.0/24, 10.0.0.0/8
# Relayhost: gateway di Microsoft 365
# Sostituisci con il tuo MX record di protezione
relayhost = [tuodominio-com.mail.protection.outlook.com]:25
# TLS obbligatorio (Office 365 richiede connessione sicura)
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
# Nessuna autenticazione SASL (usiamo IP-based via connettore M365)
smtp_sasl_auth_enable = no
# Regole per i destinatari
# permit_mynetworks: accetta da IP in mynetworks
# reject_unauth_destination: rifiuta tutto il resto (FONDAMENTALE: previene open relay)
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
4.3 Spiegazione delle direttive chiave
| Direttiva | Cosa fa | Perché è importante |
|---|---|---|
myhostname | Nome FQDN del server | Appare nei log e negli header delle mail |
myorigin | Dominio di default per mail senza @dominio | Evita mail da root@localhost |
mydestination = localhost | Non accettare mail in consegna locale | Siamo un relay, non un mail server |
mynetworks | Subnet autorizzate a usare il relay | IL MURO: solo queste reti possono inviare |
relayhost | Dove inoltrare TUTTE le mail | Le parentesi quadre disabilitano il lookup MX |
smtp_tls_security_level = encrypt | TLS obbligatorio verso M365 | Microsoft rifiuta connessioni non cifrate |
reject_unauth_destination | Rifiuta relay verso destinazioni non autorizzate | FONDAMENTALE: senza questo sei un open relay |
4.4 Trovare il relayhost corretto
Il relayhost deve puntare al record MX di protezione del tuo tenant M365. Per trovarlo:
nslookup -type=MX tuodominio.com
Cerca il record che termina con .mail.protection.outlook.com. Esempio di output:
tuodominio.com MX preference = 0, mail exchanger = tuodominio-com.mail.protection.outlook.com
Il tuo relayhost sarà: [tuodominio-com.mail.protection.outlook.com]:25
4.5 Applicare la configurazione
# Verifica sintassi
sudo postfix check
# Ricarica configurazione
sudo systemctl restart postfix
# Verifica che sia in ascolto
sudo ss -tlnp | grep :25
Output atteso:
LISTEN 0 100 0.0.0.0:25 0.0.0.0:* users:(("master",pid=1234,fd=13))
5. Configurazione del connettore M365
Questa è la parte che permette a M365 di accettare mail dal tuo relay senza autenticazione SMTP.
5.1 Accesso all’admin center
- Vai su admin.exchange.microsoft.com
- Naviga in Mail flow → Connectors
5.2 Crea un nuovo connettore
- Clicca + Add a connector
- Configura:
- Connection from: Your organization’s email server
- Connection to: Office 365
- Dai un nome descrittivo (es.
Relay_NomeDelClienteoRelay_Interno) - How to identify email sent from your email server: seleziona “By verifying that the IP address of the sending server matches one of these IP addresses”
- Inserisci gli IP pubblici del tuo relay (o range CIDR se hai più IP)
5.3 Esempio configurazione
Nel nostro caso il connettore Relay_NomeDelCliente è configurato così:
- Name: Relay_NomeDelCliente
- Status: On
- From: Your organization’s email server
- To: Office 365
- IP addresses: 123.114.132.128/28, 132.228.123.204/30 ( quello che è l’ip di provenienza che avete settato sul firewall per la macchina)
- Requirement: sender’s or recipient’s email address is an accepted domain
5.4 Considerazioni sulla sicurezza
- Inserisci solo gli IP pubblici strettamente necessari
- Se possibile, usa range CIDR stretti (es.
/30invece di/24) - Il requisito “accepted domain” evita che qualcuno usi il tuo relay per inviare mail a nome di domini che non controlli
5.5 SPF Record
Non dimenticare di aggiungere l’IP del relay al tuo record SPF:
v=spf1 include:spf.protection.outlook.com ip4:TUO.IP.PUBBLICO.QUI -all
6. Test e verifica
6.1 Test da riga di comando (dal relay stesso)
echo "Test dal relay SMTP interno" | mail -s "Test Relay" tuoindirizzo@tuodominio.com
6.2 Test con telnet/netcat (da un client nella LAN autorizzata)
nc svr-smtprelay 25
Poi digita manualmente:
HELO test.local
MAIL FROM:<test@tuodominio.com>
RCPT TO:<tuoindirizzo@tuodominio.com>
DATA
Subject: Test manuale
Corpo del messaggio di test.
.
QUIT
6.3 Verifica log in tempo reale
Sul server relay:
sudo tail -f /var/log/mail.log
Dovresti vedere qualcosa tipo:
postfix/smtp[12345]: ABC123DEF: to=<dest@example.com>, relay=tuodominio-com.mail.protection.outlook.com[52.101.x.x]:25, status=sent (250 2.0.0 OK)
6.4 Test di sicurezza: verifica che NON sia un open relay
Da un IP esterno alle mynetworks, prova a inviare. Deve fallire:
550 5.7.1 <dest@esterno.com>: Relay access denied
7. Il tool mail-mini: diagnostica per esseri umani
Grep sui log di Postfix funziona, ma dopo la terza volta che scrivi quel sed | awk | grep chilometrico, vuoi qualcosa di meglio. Ecco mail-mini.
7.1 Cosa fa
- Visualizza la coda con comandi utili suggeriti
- Ultime N consegne con timestamp, mittente, destinatario, IP sorgente, relay
- Deferred e reject formattati per capire subito cosa non va
- Ricerca per destinatario veloce
- Top destinatari per giorno o storico
7.2 Installazione
Crea il file:
sudo nano /usr/local/bin/mail-mini
Incolla il contenuto dello script (vedi sotto) e rendi eseguibile:
sudo chmod +x /usr/local/bin/mail-mini
7.3 Uso
Menu interattivo (per chi preferisce i menu):
sudo mail-mini
Comandi diretti (per chi preferisce la CLI):
# Coda Postfix
sudo mail-mini queue
# Ultime 100 mail inviate
sudo mail-mini sent 100
# Ultimi 50 deferred
sudo mail-mini deferred 50
# Cerca mail per destinatario (ultime 500 righe di log)
sudo mail-mini search to utente@example.com 500
# Top destinatari di oggi
sudo mail-mini top today
7.4 Esempio di output
ULTIME 50 CONSEGNE (status=sent)
──────────────────────────────────────────────────────────────────────────────────
DATA/ORA QUEUE-ID CLIENT-IP FROM TO RELAY
──────────────────────────────────────────────────────────────────────────────────
2025-03-25 10:15:32 A1B2C3D4E 172.10.4.55 scanner@tuodominio.com utente@tuodominio.com tuodominio-com.mail.protection.outlook.com[52.101.73.4]:25
2025-03-25 10:14:01 B2C3D4E5F 172.10.1.10 erp@tuodominio.com amministrazione@tuodominio tuodominio-com.mail.protection.outlook.com[52.101.73.4]:25
7.5 Il codice completo
Lo script è lungo ma ben commentato. Eccolo:
Clicca per espandere mail-mini (circa 250 righe bash)
#!/usr/bin/env bash
# mail-mini — Mini interfaccia CLI per Postfix (coda + storico dai log) con output leggibile
# - Timestamp corretta: supporta ISO8601 (YYYY-MM-DDTHH:MM:SS) e syslog classico (Mon dd HH:MM:SS)
# - Colonne allineate
# - IP di provenienza (client IP)
#
# Uso:
# mail-mini # menu interattivo
# mail-mini queue # coda
# mail-mini sent 100 # ultime 100 consegne
# mail-mini deferred 50 # ultimi 50 deferred
# mail-mini rejects 50 # ultimi 50 rifiuti
# mail-mini search to user@example.com [MAX_LOG_LINES]
# mail-mini top today|yesterday|all
#
set -o pipefail
LOG_YEAR="$(date +%Y)"
LOG_MONTH="$(date +%m)"
collect_plain_logs() {
local files=""
for f in /var/log/mail.log /var/log/mail.log.1; do
[ -r "$f" ] && files+=" $f"
done
echo "$files"
}
collect_all_logs() {
local files=""
for f in /var/log/mail.log /var/log/mail.log.1 /var/log/mail.log.[2-9].gz; do
[ -r "$f" ] && files+=" $f"
done
echo "$files"
}
PLAIN_LOGS="$(collect_plain_logs)"
ALL_LOGS="$(collect_all_logs)"
ZGREPOPTS="-h --no-group-separator"
need_tools() {
for c in tac grep awk sed date postqueue postsuper zgrep; do
command -v "$c" >/dev/null 2>&1 || { echo "Comando richiesto non trovato: $c"; exit 1; }
done
}
rule() { printf '%s\n' "────────────────────────────────────────────────────────────────────────────────────────────────────────"; }
hdr() { printf '\033[1m%s\033[0m\n' "$1"; rule; }
fmt_ts_line() {
local line
IFS= read -r line || return
if echo "$line" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}'; then
echo "$line" | sed -E 's/^([0-9]{4}-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2}).*/\1 \2/'
else
printf '%s\n' "$line" | awk -v Y="$LOG_YEAR" -v M="$LOG_MONTH" '
function mon2num(m){
split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec",a," ");
for(i=1;i<=12;i++) if(a[i]==m) return i;
return 1
}
{
mon=$1; day=$2; time=$3;
if (length(day)==1) day="0"day;
mn = mon2num(mon);
year = Y;
if (mn > M + 1) year = Y - 1;
printf "%04d-%02d-%s %s\n", year, mn, day, time
}'
fi
}
extract_id() { echo "$1" | sed -n 's/.* \([A-F0-9]\{7,\}\):.*/\1/p'; }
extract_from() { echo "$1" | sed -n 's/.* from=<\([^>]*\)>.*/\1/p'; }
extract_to() { echo "$1" | sed -n 's/.* to=<\([^>]*\)>.*/\1/p'; }
extract_relay() { echo "$1" | sed -n 's/.* relay=\([^,]*\).*/\1/p'; }
extract_status() { echo "$1" | sed -n 's/.* status=\([a-z][a-z]*\).*/\1/p'; }
extract_ip_from_line() { echo "$1" | sed -n 's/.*\[\([^]]*\)\].*/\1/p'; }
extract_client_ip_by_id() {
local id="$1"
[ -z "$id" ] && return
[ -z "$PLAIN_LOGS" ] && return
grep -h " $id: " $PLAIN_LOGS 2>/dev/null | grep -m1 'client=' | \
sed -n 's/.*client=[^[]*\[\([^]]*\)\].*/\1/p'
}
format_sent() {
local n="${1:-50}"
hdr "ULTIME $n CONSEGNE (status=sent)"
printf "%-19s %-12s %-16s %-40s %-42s %-30s\n" \
"DATA/ORA" "QUEUE-ID" "CLIENT-IP" "FROM" "TO" "RELAY"
rule
[ -z "$PLAIN_LOGS" ] && { echo "Nessun file di log (plain) trovato."; return; }
{ for f in $PLAIN_LOGS; do tac "$f"; done; } 2>/dev/null | \
grep 'status=sent' | head -n "$n" | \
while IFS= read -r line; do
ts="$(printf "%s\n" "$line" | fmt_ts_line)"
id="$(extract_id "$line")"
to="$(extract_to "$line")"
relay="$(extract_relay "$line")"
from="$(extract_from "$line")"
if [ -z "$from" ] && [ -n "$id" ]; then
from="$(grep -h " $id: " $PLAIN_LOGS 2>/dev/null | \
grep -m1 ' from=<' | sed -n 's/.* from=<\([^>]*\)>.*/\1/p')"
fi
ip="$(extract_client_ip_by_id "$id")"
printf "%-19s %-12s %-16s %-40s %-42s %-30s\n" \
"$ts" "$id" "${ip:-?}" "${from:-?}" "$to" "$relay"
done
}
format_deferred() {
local n="${1:-50}"
hdr "ULTIMI $n DEFERRED (temporanei, Postfix ritenta)"
printf "%-19s %-12s %-16s %-40s %-42s %-48s\n" \
"DATA/ORA" "QUEUE-ID" "CLIENT-IP" "FROM" "TO" "MOTIVO"
rule
[ -z "$PLAIN_LOGS" ] && { echo "Nessun file di log (plain) trovato."; return; }
{ for f in $PLAIN_LOGS; do tac "$f"; done; } 2>/dev/null | \
grep -E " status=deferred " | head -n "$n" | \
while IFS= read -r line; do
ts="$(printf "%s\n" "$line" | fmt_ts_line)"
id="$(extract_id "$line")"
to="$(extract_to "$line")"
from="$(extract_from "$line")"
if [ -z "$from" ] && [ -n "$id" ]; then
from="$(grep -h " $id: " $PLAIN_LOGS 2>/dev/null | \
grep -m1 ' from=<' | sed -n 's/.* from=<\([^>]*\)>.*/\1/p')"
fi
reason="$(echo "$line" | sed -n 's/.*(\(.*\)).*/\1/p')"
ip="$(extract_client_ip_by_id "$id")"
printf "%-19s %-12s %-16s %-40s %-42s %-48s\n" \
"$ts" "$id" "${ip:-?}" "${from:-?}" "$to" "$reason"
done
}
format_rejects() {
local n="${1:-50}"
hdr "ULTIMI $n RIFIUTI (reject)"
printf "%-19s %-16s %-40s %-42s %-18s %-48s\n" \
"DATA/ORA" "CLIENT-IP" "FROM" "TO" "CODICE" "MOTIVO"
rule
[ -z "$PLAIN_LOGS" ] && { echo "Nessun file di log (plain) trovato."; return; }
{ for f in $PLAIN_LOGS; do tac "$f"; done; } 2>/dev/null | \
grep -E " reject|denied " | head -n "$n" | \
while IFS= read -r line; do
ts="$(printf "%s\n" "$line" | fmt_ts_line)"
from="$(extract_from "$line")"
to="$(extract_to "$line")"
code="$(echo "$line" | sed -n 's/.* \(5\.[0-9]\+\.[0-9]\+\).*/\1/p')"
reason="$(echo "$line" | sed -n 's/.*reject: \([^;]*\).*/\1/p')"
ip="$(extract_ip_from_line "$line")"
printf "%-19s %-16s %-40s %-42s %-18s %-48s\n" \
"$ts" "${ip:-?}" "$from" "$to" "$code" "$reason"
done
}
search_to() {
local addr="$1"
local max_lines="${2:-50}"
[ -z "$addr" ] && { echo "Uso: mail-mini search to user@example.com [MAX_LOG_LINES]"; exit 1; }
hdr "RICERCA DESTINATARIO: $addr (analizzo ultime $max_lines righe di log)"
printf "%-19s %-12s %-16s %-40s %-42s %-10s %-40s\n" \
"DATA/ORA" "QUEUE-ID" "CLIENT-IP" "FROM" "TO" "STATUS" "RELAY/MOTIVO"
rule
[ -z "$PLAIN_LOGS" ] && { echo "Nessun file di log (plain) trovato."; return; }
{ for f in $PLAIN_LOGS; do tac "$f"; done; } 2>/dev/null | \
head -n "$max_lines" | \
grep " to=<${addr}>" | grep 'status=' | \
while IFS= read -r line; do
ts="$(printf "%s\n" "$line" | fmt_ts_line)"
id="$(extract_id "$line")"
from="$(extract_from "$line")"
to="$(extract_to "$line")"
status="$(extract_status "$line")"
if [ -z "$from" ] && [ -n "$id" ]; then
from="$(grep -h " $id: " $PLAIN_LOGS 2>/dev/null | \
grep -m1 ' from=<' | sed -n 's/.* from=<\([^>]*\)>.*/\1/p')"
fi
if [ "$status" = "sent" ]; then
extra="$(extract_relay "$line")"
else
extra="$(echo "$line" | sed -n 's/.*(\(.*\)).*/\1/p')"
fi
ip="$(extract_client_ip_by_id "$id")"
printf "%-19s %-12s %-16s %-40s %-42s %-10s %-40s\n" \
"$ts" "$id" "${ip:-?}" "${from:-?}" "$to" "$status" "$extra"
done
}
top_recipients() {
local range="$1" logs since=""
case "$range" in
today)
logs="$PLAIN_LOGS"
since="$(date '+%Y-%m-%d')"
echo "ATTENZIONE: analizzo i log di oggi, potrebbe volerci qualche secondo..."
;;
yesterday)
logs="$PLAIN_LOGS"
since="$(date -d 'yesterday' '+%Y-%m-%d')"
echo "ATTENZIONE: analizzo i log di ieri, potrebbe volerci qualche secondo..."
;;
all|"")
logs="$ALL_LOGS"
;;
*)
logs="$ALL_LOGS"
;;
esac
hdr "TOP DESTINATARI ${range:-all} (solo consegne riuscite)"
[ -z "$logs" ] && { echo "Nessun file di log trovato."; return; }
if [ -n "$since" ]; then
zgrep $ZGREPOPTS "status=sent" $logs 2>/dev/null | \
while IFS= read -r line; do
ts="$(printf "%s\n" "$line" | fmt_ts_line)"
printf '%s|%s\n' "$ts" "$line"
done | grep "^${since}" | \
sed -n 's/.* to=<\([^>]*\)>.*status=sent.*/\1/p' | \
awk '{c[$0]++} END{for(k in c) printf "%6d %s\n", c[k], k}' | \
sort -nr | head
else
zgrep $ZGREPOPTS "status=sent" $logs 2>/dev/null | \
sed -n 's/.* to=<\([^>]*\)>.*status=sent.*/\1/p' | \
awk '{c[$0]++} END{for(k in c) printf "%6d %s\n", c[k], k}' | \
sort -nr | head
fi
}
print_queue() {
hdr "CODA POSTFIX (postqueue -p)"
postqueue -p
printf '\nSuggerimenti: %s %s %s\n' "postqueue -f" "postsuper -d <ID>" "postsuper -r <ID>"
}
menu() {
need_tools
while true; do
echo
echo "Mail MiniUI — scegli un'azione:"
echo " 1) Vedi coda (postqueue -p)"
echo " 2) Ultime 50 consegne (status=sent)"
echo " 3) Ultimi 50 deferred"
echo " 4) Ultimi 50 rifiuti (reject)"
echo " 5) Cerca per destinatario (ultime 50 righe)"
echo " 6) Top destinatari di oggi"
echo " 7) Top destinatari di ieri"
echo " 8) Flush coda (postqueue -f)"
echo " 9) Esci"
read -rp "Selezione: " ans
case "$ans" in
1) print_queue ;;
2) format_sent 50 ;;
3) format_deferred 50 ;;
4) format_rejects 50 ;;
5) read -rp "Inserisci destinatario (es. user@example.com): " r ; search_to "$r" 50 ;;
6) top_recipients today ;;
7) top_recipients yesterday ;;
8) echo "Flush..." ; postqueue -f ;;
9) exit 0 ;;
*) echo "Scelta non valida." ;;
esac
done
}
case "$1" in
queue) need_tools; print_queue ;;
sent) need_tools; format_sent "${2:-50}" ;;
deferred) need_tools; format_deferred "${2:-50}" ;;
rejects) need_tools; format_rejects "${2:-50}" ;;
search) need_tools; shift;
if [ "$1" = "to" ]; then
shift
search_to "$1" "${2:-50}"
else
echo "Uso: mail-mini search to user@example.com [MAX_LOG_LINES]"
fi ;;
top) need_tools; top_recipients "${2:-all}" ;;
"" ) menu ;;
*) echo "Uso: mail-mini [queue|sent [N]|deferred [N]|rejects [N]|search to <addr> [MAX_LOG_LINES]|top [today|yesterday|all]]"; exit 1 ;;
esac
8. Manutenzione ordinaria
8.1 Aggiungere una rete trusted
- Backup prima di tutto:
sudo cp -a /etc/postfix/main.cf /etc/postfix/main.cf.bak.$(date +%F_%H%M)
- Modifica
main.cf:
sudo nano /etc/postfix/main.cf
- Aggiungi la subnet a
mynetworksin formato CIDR:
mynetworks = 127.0.0.0/8, 192.168.1.0/24, 192.168.2.0/24, 10.0.0.0/8
- Ricarica Postfix:
sudo systemctl restart postfix
8.2 Verificare lo stato
# Stato del servizio
sudo systemctl status postfix
# Coda attuale
sudo postqueue -p
# Oppure con mail-mini
sudo mail-mini queue
8.3 Forzare l’invio della coda
sudo postqueue -f
8.4 Eliminare un messaggio dalla coda
sudo postsuper -d QUEUE_ID
8.5 Bonus: pfqueue per gestione visuale
Se preferisci un’interfaccia TUI:
sudo apt install pfqueue
sudo pfqueue
Tasti utili: f flush, d delete, q quit.
9. Troubleshooting
9.1 Log in tempo reale
sudo tail -f /var/log/mail.log
9.2 Cercare errori per un orario specifico
# Tutte le righe delle 12:23:20 con contesto
grep -A3 -B3 "12:23:20" /var/log/mail.log
9.3 Errori comuni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
Relay access denied | IP non in mynetworks | Aggiungi la subnet del mittente |
Connection timed out | Firewall blocca porta 25 in uscita | Verifica regole firewall/NAT |
Certificate verification failed | Problema certificati CA | sudo update-ca-certificates |
Host not found | Relayhost errato | Verifica MX record con nslookup |
| Mail consegnata ma non arriva | Connettore M365 non configurato | Verifica IP whitelist nel connettore |
454 4.7.0 TLS not available | M365 non accetta la connessione | Verifica smtp_tls_security_level = encrypt |
9.4 Test connessione TLS verso M365
openssl s_client -connect tuodominio-com.mail.protection.outlook.com:25 -starttls smtp
Deve mostrare il certificato Microsoft e la connessione TLS stabilita.
10. Considerazioni finali
Sicurezza
- Mai rimuovere
reject_unauth_destination— è quello che ti protegge dall’essere un open relay - Limita mynetworks alle sole subnet che ne hanno davvero bisogno
- Firewall: blocca la porta 25 dall’esterno, solo la LAN deve raggiungerla
Perché Postfix e non sendmail/exim/altro?
- Postfix è leggero, veloce, e ha una configurazione comprensibile da esseri umani
- È il default su Ubuntu e ben mantenuto
- La documentazione è eccellente
Evoluzione possibile
- Aggiungere la generic map per riscrivere mittenti fantasiosi (tipo
root@localhost) — il file c’è già, basta attivarlo - Monitoraggio con Zabbix/Prometheus: esporta metriche dalla coda Postfix
- Rate limiting se qualche device impazzisce e inizia a sparare mail
Replicare su altre sedi
La stessa configurazione funziona identica su altre sedi. L’unica differenza è:
- IP diverso in
mynetworksper le reti locali - IP pubblico diverso nel connettore M365
Nel nostro caso abbiamo un relay identico anche sull’infrastruttura secondaria del cliente (172.19.1.15) con lo stesso setup.
Articolo scritto nel 2026 per riccardorenda.it — perché documentare è importante, soprattutto quando parli con te stesso del futuro.
