Guida di costruzione
Costruire il laboratorioDa una macchina virtuale vuota a un Exchange ibrido
Prima di cominciare
Questo manuale porta da un hypervisor vuoto a un Exchange 2019 in alta disponibilità, con trasporto perimetrale, bilanciatore e sincronizzazione verso Microsoft 365. Nove parti, in ordine: ogni parte presuppone quella prima.
Il compagno di questo documento è il manuale del laboratorio, che spiega perché ogni pezzo è fatto così. Qui c'è il come. Quando una scelta ha una motivazione lunga, questo manuale la riassume in una riga e rimanda là.
#Cosa serve prima del primo comando
| Voce | Requisito | Note |
|---|---|---|
| Hypervisor | Proxmox VE 9 su una macchina fisica | 28 thread, 32 GB di RAM, disco SSD |
| Memoria | 38 GB a pieno regime | Più di quella fisica: si lavora a gruppi di accensione |
| ISO | Windows Server 2022, Exchange 2019 CU15, pfSense CE 2.8, Debian 13 netinst | |
| Licenze | Nessuna: Exchange in Standard Evaluation, Windows in valutazione | 180 giorni |
| Tenant | Un tenant Microsoft 365 con un dominio pubblico verificato | Serve un sottodominio verificabile |
| Tempo | Una settimana di sere, non un pomeriggio | La parte lunga è l'ibrido |
#L'ordine dei passi, e perché è quello
Ogni fase esiste perché la successiva la richiede. Saltare avanti fa fallire cose che sembrano non c'entrare.
- Rete — prima di tutto. Senza segmenti e instradamento, nessuna macchina si parla.
- Dominio — autenticazione e DNS. Exchange non si installa senza.
- Identità — le unità organizzative e gli attributi vanno decisi prima di creare utenti, perché spostarli dopo genera eliminazioni nel cloud.
- Exchange — schema, primo server, secondo server, database, DAG.
- Perimetro — richiede Exchange installato e l'ora allineata.
- Bilanciatore — richiede i due Exchange in ascolto.
- Ibrido — richiede tutto il resto, più un dominio verificato.
#Le convenzioni, decise una volta
Vanno stabilite prima del primo comando, perché entrano in ogni nome.
| Convenzione | Valore | Perché |
|---|---|---|
| Prefisso macchine | LAB- | Riconoscibile a colpo d'occhio nell'elenco dell'hypervisor |
| Identificativi | 1300–1399 | Intervallo riservato, non collide con altro |
| Pool | LAB-EXCHANGE | Permessi e gestione in blocco |
| Dominio interno | contoso.lab | Di fantasia: un comando di laboratorio non può colpire la produzione |
| NetBIOS | CONTOSO | |
CN degli oggetti | uguale al samAccountName | Non è estetica: metà dei permessi dipende da questo |
Parte 1 — L'host e le macchine
#Il dimensionamento completo
Questa è la tabella da cui parte tutto. I valori sono quelli in esercizio.
| ID | Nome | Ruolo | Bridge | Indirizzo | RAM | Disco |
|---|---|---|---|---|---|---|
| 1300 | LAB-DC01 | AD DS, DNS, testimone DAG | vmbr90 | 10.20.10.10/24 | 3 GB | 60 GB |
| 1301 | LAB-MBX01 | Exchange, nodo DAG | vmbr91 | 10.20.20.10/29 | 8 GB | 80 GB |
| 1302 | LAB-MBX02 | Exchange, nodo DAG | vmbr91 | 10.20.20.11/29 | 8 GB | 80 GB |
| 1303 | LAB-LB01 | HAProxy | vmbr92 | 10.20.20.34/29 + VIP .38 | 2 GB | 20 GB |
| 1304 | LAB-CLI01 | Client applicativo | vmbr93 | 10.20.30.10/24 | 4 GB | 60 GB |
| 1305 | LAB-EDG01 | Trasporto perimetrale | vmbr94 | 10.20.40.2/29 | 4 GB | 80 GB |
| 1306 | LAB-EDG02 | Trasporto perimetrale | vmbr94 | 10.20.40.3/29 | 4 GB | 80 GB |
| 1310 | LAB-FW01 | Firewall pfSense | tutti | vedi Parte 2 | 1 GB | 16 GB |
| 1311 | LAB-SYNC01 | Entra Connect | vmbr90 | 10.20.10.40/24 | 4 GB | 60 GB |
Gli 8 GB sui due Exchange non sono generosi: sono il minimo sotto cui l'installazione protesta e il servizio diventa inutilizzabile.
#I bridge isolati
Cinque bridge Linux senza porte fisiche. È l'isolamento per costruzione: un bridge senza porte non ha alcun percorso verso le schede dell'host.
| Bridge | Porte | Segmento |
|---|---|---|
vmbr0 | scheda fisica | Rete esistente — unico contatto con l'esterno |
vmbr90 | nessuna | Gestione |
vmbr91 | nessuna | Exchange |
vmbr92 | nessuna | Bilanciatore |
vmbr93 | nessuna | Client |
vmbr94 | nessuna | DMZ |
Creazione, in /etc/network/interfaces sull'host:
auto vmbr90
iface vmbr90 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0Ripetere per vmbr91…vmbr94, poi applicare e verificare:
ifreload -a
ip -br a | grep vmbr9 # nessun IPv4 sui bridge del laboratorio
bridge link show | grep vmbr9 # nessuna riga = nessuna porta agganciata#Creare una macchina
Ogni macchina Windows del laboratorio nasce così. I valori variabili sono identificativo, nome, memoria, disco e bridge.
qm create 1300 --name LAB-DC01 --pool LAB-EXCHANGE --tags lab `
--ostype win11 --machine q35 --bios ovmf `
--cores 2 --sockets 1 --memory 3072 --balloon 0 `
--net0 virtio,bridge=vmbr90,firewall=1 `
--scsihw virtio-scsi-single `
--scsi0 local-lvm:60,discard=on,ssd=1 `
--efidisk0 local-lvm:1,efitype=4m,pre-enrolled-keys=1 `
--ide2 local:iso/WindowsServer2022.iso,media=cdrom `
--ide0 local:iso/virtio-win.iso,media=cdrom `
--boot order='ide2;scsi0'Tre dettagli che fanno perdere tempo se sbagliati:
--balloon 0disattiva il ballooning. Sui server Exchange è obbligatorio: la memoria dinamica fa comportamenti incoerenti sotto carico.- Il secondo CD-ROM con i driver VirtIO serve durante l'installazione di Windows, che altrimenti non vede il disco. È il punto in cui ci si blocca al primo tentativo.
--ostype win11conq35e UEFI è la combinazione che funziona con Server 2022.
#I limiti di CPU e memoria
Vanno messi subito, prima di accendere: servono a non far soffocare quello che gira già sull'host.
qm set <vmid> --cpulimit 2 # tetto di 2 core effettivi
qm set <vmid> --cpuunits 50 # metà del peso predefinito#Il fuso orario, prima di ogni altra cosa
Va fatto su ogni macchina Windows, subito dopo l'installazione del sistema, e deve essere lo stesso dell'host.
Set-TimeZone -Id "W. Europe Standard Time"Parte 2 — Il firewall
Prima di tutto il resto: senza instradamento fra i segmenti, nessuna macchina vede le altre.
#Installare pfSense
La macchina ha sei schede di rete, una per segmento più l'uscita. Vanno tutte assegnate alla creazione, perché su FreeBSD non si agganciano a caldo.
qm create 1310 --name LAB-FW01 --pool LAB-EXCHANGE --tags lab `
--ostype l26 --cores 2 --memory 1024 `
--net0 virtio,bridge=vmbr90,firewall=0 `
--net1 virtio,bridge=vmbr91,firewall=0 `
--net2 virtio,bridge=vmbr92,firewall=0 `
--net3 virtio,bridge=vmbr93,firewall=0 `
--net4 virtio,bridge=vmbr0,firewall=0 `
--net5 virtio,bridge=vmbr94,firewall=0 `
--scsihw virtio-scsi-single --scsi0 local-lvm:16,discard=on `
--ide2 local:iso/pfSense-CE-2.8.1.iso,media=cdrom `
--boot order='ide2;scsi0'Se una scheda va aggiunta dopo, la sequenza è: snapshot, qm set, spegnimento e riaccensione — non un riavvio a caldo.
qm snapshot 1310 pre-modifica
qm set 1310 -net6 virtio,bridge=vmbr9X,firewall=1
qm shutdown 1310 && qm start 1310#Assegnare le interfacce
Nella console, poi da interfaccia web: Interfaces → Assignments, una riga per scheda.
| Interfaccia | Scheda | Bridge | Indirizzo |
|---|---|---|---|
DC | vtnet0 | vmbr90 | 10.20.10.1/24 |
EXCH | vtnet1 | vmbr91 | 10.20.20.9/29 |
LB | vtnet2 | vmbr92 | 10.20.20.33/29 |
CLIENT | vtnet3 | vmbr93 | 10.20.30.1/24 |
WAN | vtnet4 | vmbr0 | DHCP dalla rete esistente |
DMZ | vtnet5 | vmbr94 | 10.20.40.1/29 |
Ogni interfaccia va aperta, spuntata Enable, descritta e indirizzata staticamente. La WAN resta in DHCP.
#Le regole, e le tre caselle da togliere
Su ciascuna interfaccia interna una sola regola Pass, con Protocol: Any, Source: Any, Destination: Any.
Regole permissive fra i segmenti sono una scelta: il laboratorio serve a studiare Exchange, non a esercitarsi sulla segmentazione. La protezione verso l'esterno sta sulla WAN.
Poi le tre impostazioni che generano guasti dall'aria misteriosa:
Block private networks e Block bogon networks, sulla WAN. Vanno tolte. Quei filtri hanno senso verso internet vero; qui la «WAN» è una LAN privata, e il traffico amministrativo verrebbe scartato prima di qualsiasi regola. Gli inoltri di porta risultano configurati e inerti.
**DNS Hostname in System → General Setup. Va lasciato vuoto**: serve alla verifica TLS del resolver. Metterci un indirizzo IP — errore facile, il campo è adiacente — impedisce la risoluzione pur essendo l'instradamento corretto.
Filter rule association su ogni inoltro di porta. Deve restare su Add associated filter rule. Con None pfSense fa il NAT e blocca il traffico.
#L'uscita verso internet
System → General Setup: gateway predefinito sulla WAN, DNS 1.1.1.1 e 8.8.8.8, resolver attivo. NAT in uscita in modalità automatica: pfSense genera da sé le regole di mascheramento per tutte le reti interne.
Il risultato è che ogni macchina esce dietro un solo indirizzo, e nulla del laboratorio è raggiungibile dall'esterno se non attraverso inoltri espliciti.
#Verifica dell'instradamento
Dal domain controller, quando esisterà, tutti i gateway devono rispondere:
ping 10.20.10.1 ; ping 10.20.20.9 ; ping 10.20.20.33 ; ping 10.20.30.1 ; ping 10.20.40.1Il valore TTL nelle risposte distingue i percorsi: 128 da un host Windows significa percorso diretto, 127 significa un salto — cioè il passaggio dal firewall.
Parte 3 — Il dominio
#Preparare il domain controller
Installato Windows Server 2022 con esperienza desktop, prima di promuovere:
Set-TimeZone -Id "W. Europe Standard Time"
Rename-Computer -NewName LAB-DC01 -RestartAl riavvio, indirizzo statico — il DNS punta a sé stesso, e questo è corretto solo dopo la promozione, ma si imposta subito:
New-NetIPAddress -InterfaceAlias 'Ethernet' -IPAddress 10.20.10.10 `
-PrefixLength 24 -DefaultGateway 10.20.10.1
Set-DnsClientServerAddress -InterfaceAlias 'Ethernet' -ServerAddresses 127.0.0.1#Promuovere la foresta
Install-WindowsFeature AD-Domain-Services,DNS -IncludeManagementTools
Install-ADDSForest -DomainName "contoso.lab" -DomainNetbiosName "CONTOSO" `
-ForestMode WinThreshold -DomainMode WinThreshold `
-InstallDns:$true -CreateDnsDelegation:$false `
-DatabasePath "C:\Windows\NTDS" -LogPath "C:\Windows\NTDS" `
-SysvolPath "C:\Windows\SYSVOL" -NoRebootOnCompletion:$false -ForceUn solo domain controller. Non esiste un secondo, quindi il ripristino di uno snapshot non comporta i problemi di replica tipici degli ambienti con più DC — ma la sua indisponibilità ferma l'autenticazione di tutto.
#DNS: forwarder e record statici
Senza forwarder le macchine non risolvono nomi pubblici, e la conseguenza è che gli installer non scaricano pacchetti e Entra Connect non raggiunge Microsoft.
Set-DnsServerForwarder -IPAddress 1.1.1.1,8.8.8.8 -PassThruI server nel dominio si registrano da soli. Le macchine perimetrali, essendo in workgroup, no: vanno aggiunte a mano, e senza questi record la sottoscrizione perimetrale fallisce.
dnscmd . /RecordAdd contoso.lab lab-edg01 A 10.20.40.2
dnscmd . /RecordAdd contoso.lab lab-edg02 A 10.20.40.3#Il cestino di Active Directory
Da attivare subito, perché l'operazione è irreversibile e più tardi la si dimentica.
Enable-ADOptionalFeature -Identity 'Recycle Bin Feature' `
-Scope ForestOrConfigurationSet -Target 'contoso.lab' -Confirm:$falseConsente di ripristinare un oggetto cancellato per errore con tutti i suoi attributi. Senza, la cancellazione si propaga nel cloud e il recupero diventa laborioso.
Get-ADObject -Filter 'SamAccountName -eq "<utente>"' -IncludeDeletedObjects |
Restore-ADObject#Il suffisso UPN aggiuntivo
Passo obbligatorio per l'ibrido, e va fatto prima di creare gli utenti.
Get-ADForest | Set-ADForest -UPNSuffixes @{add="lab.impicciando.it"}
Get-ADForest | Select-Object -ExpandProperty UPNSuffixescontoso.lab non è instradabile su internet, ed Entra Connect rifiuta di sincronizzare UPN non instradabili. Gli utenti destinati al cloud avranno UPN sul dominio pubblico; il samAccountName e l'accesso a Windows restano invariati.
#La gerarchia oraria
Il domain controller è la radice. Tutto il resto pende da lui.
Internet (time.windows.com, pool.ntp.org)
│
LAB-DC01 → radice, marcato /reliable:yes
│
├── membri del dominio → domhier (automatico)
└── macchine in workgroup → puntamento manuale al DCSul domain controller:
w32tm /config /manualpeerlist:"time.windows.com,0x8 pool.ntp.org,0x8" /syncfromflags:manual /reliable:yes /update
Set-Service w32time -StartupType Automatic
Restart-Service w32time
w32tm /resync /rediscover
w32tm /query /statusSui membri del dominio, dopo l'aggiunta al dominio:
w32tm /config /syncfromflags:domhier /update
Restart-Service w32time
w32tm /resync
w32tm /query /source # deve rispondere col nome del DC#Disattivare IE Enhanced Security
Serve più avanti, per Entra Connect: la protezione avanzata blocca la finestra di autenticazione Microsoft e la schermata resta bianca, senza spiegazioni.
$adminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\InstalledComponents\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
$userKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\InstalledComponents\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"
Set-ItemProperty -Path $adminKey -Name "IsInstalled" -Value 0
Set-ItemProperty -Path $userKey -Name "IsInstalled" -Value 0
Stop-Process -Name Explorer -ForceParte 4 — Le identità
Questa parte va fatta prima di creare qualunque utente. Spostare oggetti dopo che la sincronizzazione è attiva genera eliminazioni nel cloud.
#Le nove unità organizzative
La struttura riflette un ciclo di vita completo dell'utenza.
$base = "DC=contoso,DC=lab"
'OPERATIVI','SEDE','SUPPORTO','ESTERNI','CASELLE',
'DISMESSI','DISABILITATI','ELIMINAZIONE','SYNC' |
ForEach-Object { New-ADOrganizationalUnit -Name $_ -Path $base -ProtectedFromAccidentalDeletion $true }SYNC ha una funzione diversa dalle altre: è il recinto della sincronizzazione. Entra Connect guarderà esclusivamente quella, quindi qualunque oggetto fuori da essa, per il tenant, non esiste. Finché l'ambito resta ristretto, un errore di configurazione può interessare al massimo gli oggetti di prova.
#Gli attributi che decidono
Due attributi guidano tutto il resto. Vanno scelti ora perché entrano nella regola di sincronizzazione e negli script.
| Attributo | Vista Exchange | Valori | Uso |
|---|---|---|---|
employeeType | — | Interno, Consulente, Funzione, Esterno | Discrimina i rami del provisioning e la regola di sincronizzazione |
extensionAttribute1 | CustomAttribute1 | SYNC365, NOSYNC | Ammette alla sincronizzazione chi non è Interno |
extensionAttribute2 | CustomAttribute2 | data | Cessazione, usata dai processi di dismissione |
employeeType fa parte dello schema standard. Gli extensionAttribute arrivano con l'estensione di schema di Exchange, che si applica nella Parte 5 — quindi la loro valorizzazione va dopo.
#Creare utenti e gruppi
La regola che vale più di ogni altra in questa parte: il CN deve coincidere col samAccountName.
New-ADUser -Name "mario.rossi" -DisplayName "Mario Rossi" `
-SamAccountName "mario.rossi" `
-UserPrincipalName "[email protected]" `
-Path "OU=SYNC,DC=contoso,DC=lab" `
-OtherAttributes @{employeeType="Interno"} `
-AccountPassword (Read-Host -AsSecureString "Password") -Enabled $trueIl nome leggibile va nel DisplayName, che è quanto compare in rubrica. Se il CN diverge, Add-ADPermission — che risolve per nome oggetto — fallisce con wasn't found, mentre Add-MailboxPermission funziona: si ottiene uno script che assegna metà dei permessi senza fermarsi.
Verifica e correzione degli oggetti non conformi:
Get-ADUser -SearchBase "OU=SYNC,DC=contoso,DC=lab" -Filter * -Properties Name |
Where-Object { $_.Name -ne $_.SamAccountName } |
ForEach-Object { Rename-ADObject -Identity $_.DistinguishedName -NewName $_.SamAccountName }I gruppi devono essere Universali di Sicurezza, non di distribuzione: sono gruppi di sicurezza abilitati alla posta, e gli script verificano esplicitamente il tipo.
New-ADGroup -Name "Gufficio.acquisti" -SamAccountName "Gufficio.acquisti" `
-GroupCategory Security -GroupScope Universal `
-Path "OU=SYNC,DC=contoso,DC=lab"Parte 5 — Exchange
#I prerequisiti
Su entrambi i futuri server cassette, dopo l'aggiunta al dominio:
Install-WindowsFeature Server-Media-Foundation, NET-Framework-45-Features, `
RPC-over-HTTP-proxy, RSAT-Clustering, RSAT-Clustering-CmdInterface, `
RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell, WAS-Process-Model, `
Web-Asp-Net45, Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, `
Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, Web-Http-Logging, `
Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, `
Web-Lgcy-Mgmt-Console, Web-Metabase, Web-Mgmt-Console, Web-Mgmt-Service, `
Web-Net-Ext45, Web-Request-Monitor, Web-Server, Web-Stat-Compression, `
Web-Static-Content, Web-Windows-Auth, Web-WMI, Windows-Identity-Foundation `
-RestartPiù i redistributable Visual C++ 2012 e 2013 x64, e il Unified Communications Managed API. .NET Framework 4.8 è già presente in Server 2022.
#Estendere lo schema
Si esegue una volta sola, dal supporto di installazione, con un account che sia Schema Admin ed Enterprise Admin.
.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /PrepareSchema
.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /PrepareAD /OrganizationName:"CONTOSO"
.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /PrepareAllDomainsVerifica dell'avvenuta estensione:
Get-ADObject "CN=ms-Exch-Schema-Version-Pt,CN=Schema,CN=Configuration,DC=contoso,DC=lab" -Properties rangeUpper |
Select-Object rangeUpperIl nome dell'organizzazione entra nel gruppo amministrativo e non si cambia più.
#Installare i due server cassette
.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF `
/Mode:Install /Role:Mailbox /InstallWindowsComponentsPoi lo stesso sul secondo server.
#Il vincolo dei cinque database
L'edizione Standard — e la valutazione si comporta come Standard — consente al massimo cinque database di cassette postali per server. Il limite conta i database presenti, copie comprese, non quelli attivi.
Andare oltre produce:
RcrExceedDbLimitException: ... maximum databases limit of 5L'architettura che sta nel limite e dà un DAG bilanciato è quattro database con copia incrociata: quattro oggetti per server.
| Database | Attivo su | Copia su | Preferenza |
|---|---|---|---|
DB01 | LAB-MBX01 | LAB-MBX02 | MBX01 = 1 |
DB02 | LAB-MBX02 | LAB-MBX01 | MBX02 = 1 |
DB03 | LAB-MBX01 | LAB-MBX02 | MBX01 = 1 |
DB04 | LAB-MBX02 | LAB-MBX01 | MBX02 = 1 |
#Creare i database
New-MailboxDatabase -Name DB01 -Server LAB-MBX01 `
-EdbFilePath "C:\ExchDB\DB01\DB01.edb" -LogFolderPath "C:\ExchDB\DB01\Logs"
New-MailboxDatabase -Name DB03 -Server LAB-MBX01 `
-EdbFilePath "C:\ExchDB\DB03\DB03.edb" -LogFolderPath "C:\ExchDB\DB03\Logs"
New-MailboxDatabase -Name DB02 -Server LAB-MBX02 `
-EdbFilePath "C:\ExchDB\DB02\DB02.edb" -LogFolderPath "C:\ExchDB\DB02\Logs"
New-MailboxDatabase -Name DB04 -Server LAB-MBX02 `
-EdbFilePath "C:\ExchDB\DB04\DB04.edb" -LogFolderPath "C:\ExchDB\DB04\Logs"
Get-MailboxDatabase | ForEach-Object { Mount-Database $_.Name }Il servizio informazioni va riavviato dopo la creazione, altrimenti i database nuovi non risultano montabili:
Restart-Service MSExchangeIS#Il DAG
Prima il testimone, sul domain controller. La cartella e la condivisione vanno create a mano, e il gruppo dei server Exchange deve avere il controllo completo.
New-Item -ItemType Directory C:\DAG1_FSW
New-SmbShare -Name DAG1.contoso.lab -Path C:\DAG1_FSW `
-FullAccess "CONTOSO\Exchange Trusted Subsystem"
Add-LocalGroupMember -Group Administrators -Member "CONTOSO\Exchange Trusted Subsystem"Poi il gruppo, senza indirizzo IP: è il modello consigliato da Exchange 2013 in poi.
New-DatabaseAvailabilityGroup -Name DAG1 `
-WitnessServer LAB-DC01.contoso.lab -WitnessDirectory C:\DAG1_FSW `
-DatabaseAvailabilityGroupIPAddresses ([System.Net.IPAddress]::None)
Add-DatabaseAvailabilityGroupServer -Identity DAG1 -MailboxServer LAB-MBX01
Add-DatabaseAvailabilityGroupServer -Identity DAG1 -MailboxServer LAB-MBX02#Le copie
Ogni database va copiato sull'altro nodo, con preferenza di attivazione 2.
Add-MailboxDatabaseCopy -Identity DB01 -MailboxServer LAB-MBX02 -ActivationPreference 2
Add-MailboxDatabaseCopy -Identity DB03 -MailboxServer LAB-MBX02 -ActivationPreference 2
Add-MailboxDatabaseCopy -Identity DB02 -MailboxServer LAB-MBX01 -ActivationPreference 2
Add-MailboxDatabaseCopy -Identity DB04 -MailboxServer LAB-MBX01 -ActivationPreference 2Il seeding parte da solo. Verifica:
Get-MailboxDatabaseCopyStatus * |
ft Name,Status,ActiveCopy,CopyQueueLength,ReplayQueueLength,ContentIndexState -AutoMounted è la copia attiva, Healthy una passiva allineata. Entrambe le code a zero significa replica in pari.
Test-ReplicationHealth -Identity LAB-MBX01 | Where-Object Result -ne 'Passed'#I domini accettati
| Dominio | Tipo | Scopo |
|---|---|---|
contoso.lab | Authoritative | Dominio interno, predefinito |
lab.impicciando.it | Authoritative | Dominio verificato nel tenant — indirizzi primari |
<tenant>.mail.onmicrosoft.com | InternalRelay | Instradamento verso Exchange Online |
New-AcceptedDomain -Name "lab.impicciando.it" -DomainName "lab.impicciando.it" -DomainType Authoritative
New-AcceptedDomain -Name "routing" -DomainName "<tenant>.mail.onmicrosoft.com" -DomainType InternalRelayIl dominio di routing è ciò che rende possibile Enable-RemoteMailbox: senza, Exchange rifiuta l'indirizzo di instradamento. InternalRelay è il tipo corretto per un dominio condiviso fra locale e cloud.
#Disattivare la politica degli indirizzi
Su ogni oggetto gestito dagli script.
Set-Mailbox <utente> -EmailAddressPolicyEnabled $false `
-PrimarySmtpAddress <utente>@lab.impicciando.itFinché la politica è attiva è lei a decidere l'indirizzo primario, e ogni tentativo di impostarlo a mano fallisce. Peggio: gli indirizzi vengono ricalcolati su contoso.lab, che nel tenant non è verificabile, ed Entra li scarta assegnando d'ufficio un indirizzo di servizio. Il ragionamento completo: Chi decide l'indirizzo primario.
#I connettori applicativi
Due connettori riproducono la coppia tipica della produzione, e la differenza spiega due errori distinti.
| Anonimo | Autenticato | |
|---|---|---|
| Porta | 25 | 587 |
| Permessi | AnonymousUsers + relay esplicito | ExchangeUsers |
| Controllo | Solo indirizzo sorgente | Indirizzo e credenziali |
| Errore tipico | 550 se l'indirizzo non è in elenco | 530 5.7.57 se non ci si autentica |
New-ReceiveConnector -Name "smtp-app-lab" -TransportRole FrontendTransport -Server LAB-MBX01 `
-Bindings 0.0.0.0:25 `
-RemoteIPRanges 10.20.20.10,10.20.20.11,10.20.20.34,10.20.30.10,fe80::/64 `
-PermissionGroups AnonymousUsers -AuthMechanism Tls -Enabled $true
Get-ReceiveConnector "LAB-MBX01\smtp-app-lab" |
Add-ADPermission -User "NT AUTHORITY\ANONYMOUS LOGON" `
-ExtendedRights "Ms-Exch-SMTP-Accept-Any-Recipient"Il secondo comando è quello che si dimentica: senza il diritto esteso, un invio verso l'esterno riceve 550 5.7.54 anche con tutto il resto corretto.
fe80::/64 è negli intervalli fin da subito, e non è pedanteria: un connettore con soli intervalli IPv4 non viene mai selezionato per una connessione IPv6, che finisce su Default Frontend e viene giustamente rifiutata. La storia: 550 5.7.54 con il connettore giusto.
Infine i log di protocollo, che serviranno in ogni diagnosi successiva:
Get-ReceiveConnector -Server LAB-MBX01 | Set-ReceiveConnector -ProtocolLoggingLevel VerboseParte 6 — Il perimetro
Due macchine fuori dal dominio, in DMZ. Non contattano mai Active Directory: conservano una copia della configurazione in AD LDS, popolata da EdgeSync.
#Preparare le macchine in workgroup
Dopo l'installazione del sistema, senza aggiunta al dominio:
Set-TimeZone -Id "W. Europe Standard Time"
Rename-Computer -NewName LAB-EDG01 -RestartOra, indirizzo e DNS — che punta al domain controller anche se la macchina non è nel dominio:
New-NetIPAddress -InterfaceAlias 'Ethernet' -IPAddress 10.20.40.2 `
-PrefixLength 29 -DefaultGateway 10.20.40.1
Set-DnsClientServerAddress -InterfaceAlias 'Ethernet' -ServerAddresses 10.20.10.10
Set-NetConnectionProfile -NetworkCategory PrivateOrario, con puntamento manuale perché non c'è gerarchia di dominio da seguire:
w32tm /config /manualpeerlist:"10.20.10.10,0x8" /syncfromflags:manual /update
Set-Service w32time -StartupType Automatic
Restart-Service w32time
w32tm /resync#Il suffisso DNS primario
Il passo che, saltato, fa fallire la sottoscrizione con un errore che non lo nomina.
$k = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters'
Set-ItemProperty $k -Name 'Domain' -Value 'contoso.lab'
Set-ItemProperty $k -Name 'NV Domain' -Value 'contoso.lab'
Restart-ComputerUna macchina in workgroup non eredita il suffisso dal dominio. Senza queste due chiavi il nome completo resta il solo nome NetBIOS. Verifica:
[System.Net.Dns]::GetHostEntry($env:COMPUTERNAME).HostNameDeve rispondere lab-edg01.contoso.lab. Se risponde LAB-EDG01, la sottoscrizione fallirà.
#Installare il ruolo
Install-WindowsFeature ADLDSPiù i redistributable Visual C++ 2012 e 2013 x64. Poi:
.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /Mode:Install /Role:EdgeTransport#La sottoscrizione EdgeSync
Sulla macchina perimetrale — il file contiene credenziali ed è valido 1440 minuti:
New-EdgeSubscription -FileName "C:\EdgeSubscription-LAB-EDG01.xml"Trasferimento del file — la macchina è in workgroup, quindi serve autenticazione esplicita:
net use Z: \\lab-mbx01.contoso.lab\C$ /user:CONTOSO\Administrator
Copy-Item C:\EdgeSubscription-LAB-EDG01.xml Z:\
net use Z: /deleteSul server cassette:
Get-ExchangeServer LAB-MBX01 | fl Name,Site # per il nome del sito
New-EdgeSubscription -FileData ([byte[]]$(Get-Content -Path "C:\EdgeSubscription-LAB-EDG01.xml" -Encoding Byte -ReadCount 0)) -Site "Default-First-Site-Name"
Start-EdgeSynchronization
Test-EdgeSynchronization#Quando non parte: i tre punti
| Stato | Significato |
|---|---|
SyncStatus : Normal, CookieRecords > 0 | Funzionante |
SyncStatus : Inconclusive, tutti NotStarted | Atteso subito dopo la creazione: il primo ciclo non è ancora avvenuto |
CouldNotConnect, The LDAP server is unavailable | Orario, oppure servizi da riavviare |
Stati Skipped | Normale: nulla di nuovo da sincronizzare |
Il suffisso DNS, se il nome completo non è quello giusto.
L'orologio. EdgeSync usa certificati auto-firmati valutati rispetto all'ora corrente: uno scarto di pochi minuti li rende non validi, e l'errore parla di connessione, non di tempo.
I servizi. Anche con ora corretta e porta aperta, EdgeSync può non partire finché non si riavviano i servizi che valutano i certificati:
Restart-Service ADAM_MSExchange -Force
Restart-Service MSExchangeEdgeCredential
Restart-Service MSExchangeTransportVerifica della porta, dal server cassette:
Test-NetConnection 10.20.40.2 -Port 50636 -InformationLevel Quiet#La prova del percorso
Un messaggio verso un dominio esterno inesistente deve restare in coda in stato Retry con NextHopDomain valorizzato: è il risultato atteso, e dimostra che il percorso interno → perimetro funziona.
Get-Queue | ft Identity,DeliveryType,Status,MessageCount,NextHopDomain -AutoRipetuta la procedura sulla seconda macchina, il connettore in uscita elenca entrambe e il failover funziona senza intervento: spegnendo la prima, la posta continua a uscire dalla seconda.
Parte 7 — Il bilanciatore
Riproduce il comportamento di un bilanciatore commerciale: riceve su un indirizzo virtuale e inoltra ai server Exchange in SNAT, così l'indirizzo sorgente visto da Exchange appartiene a un /29 diverso da quello dei nodi.
#Debian minimale e il VIP
Installazione netinst senza interfaccia grafica. Il VIP è un alias sull'interfaccia principale, in /etc/network/interfaces:
auto ens18:0
iface ens18:0 inet static
address 10.20.20.38
netmask 255.255.255.248ifup ens18:0
ip -br a # ens18 deve mostrare sia .34 sia .38#HAProxy
apt install haproxyConfigurazione in /etc/haproxy/haproxy.cfg — ventinove righe, di cui due contano davvero:
global
log /dev/log local0
maxconn 4096
daemon
defaults
log global
mode tcp
option tcplog
timeout connect 10s
timeout client 5m
timeout server 5m
frontend ft_smtp
bind 10.20.20.38:25
default_backend bk_smtp
backend bk_smtp
balance roundrobin
source 10.20.20.34
server mbx01 10.20.20.10:25 check
server mbx02 10.20.20.11:25 check
frontend ft_https
bind 10.20.20.38:443
default_backend bk_https
backend bk_https
balance roundrobin
source 10.20.20.34
server mbx01 10.20.20.10:443 check
server mbx02 10.20.20.11:443 checkmode tcp — livello 4, nessuna terminazione TLS: l'handshake attraversa il bilanciatore intatto e arriva a Exchange. In modalità HTTP il bilanciatore terminerebbe la connessione, e i fenomeni TLS da studiare non sarebbero più osservabili.
source 10.20.20.34 — forza l'indirizzo sorgente verso i server. È il SNAT.
#Verifica e prova del SNAT
Nessun output dal primo comando significa configurazione valida:
haproxy -c -f /etc/haproxy/haproxy.cfg
systemctl restart haproxy
ss -lntp | grep -E ':25|:443'
telnet 10.20.20.38 25L'ultimo deve rispondere col banner di uno dei due Exchange. Poi, nel log di protocollo di Exchange, la riga che dimostra che il laboratorio è fedele:
2026-08-19T00:23:03.808Z,LAB-MBX02\Default Frontend LAB-MBX02,...,
10.20.20.11:25,10.20.20.34:37058,+,,L'indirizzo remoto registrato è quello del bilanciatore, non del client.
Parte 8 — L'ibrido
#Il tenant e il sottodominio
Serve un tenant Microsoft 365 con un dominio pubblico già verificato. Il laboratorio usa un sottodominio di quello.
Il sottodominio viene verificato automaticamente, senza record TXT: Microsoft eredita la prova di proprietà dal dominio padre già verificato nello stesso tenant. In fase di aggiunta si salta deliberatamente il passo dei servizi — nessun record MX, CNAME o SPF — perché al laboratorio serve solo che il dominio sia verificato, non che instradi posta.
#Installare Entra Connect
Sulla macchina dedicata, nel dominio, con IE Enhanced Security già disattivata.
Nella procedura guidata, in modalità personalizzata:
| Passo | Scelta |
|---|---|
| Metodo di accesso | Password Hash Synchronization |
| Single sign-on | Disattivato |
| Foresta | contoso.lab, con un account amministrativo |
| Filtro | Solo OU=SYNC |
| Ancoraggio | mS-DS-ConsistencyGuid |
| Funzioni opzionali | Password Hash Sync, Exchange hybrid deployment |
| Avvio | Lasciare spuntato Start the synchronization process |
Import-Module ADSync
Get-ADSyncScheduler | fl SyncCycleEnabled,StagingModeEnabled,NextSyncCycleStartTimeInUTC
Set-ADSyncScheduler -SyncCycleEnabled $trueIl filtro per unità organizzativa è la protezione principale del tenant, e va riverificato a ogni riesecuzione: la procedura ripropone le impostazioni correnti ma non impedisce di modificarle per distrazione.
#Exchange hybrid deployment
Se non è stata attivata durante l'installazione: Azure AD Connect → Customize synchronization options → Optional features.
Senza, Exchange Online non riconosce gli oggetti locali come destinatari validi, e ogni assegnazione di deleghe fallisce con not found in EXO. Con, gli stessi oggetti compaiono come MailUser.
Abilita anche la scrittura all'indietro di alcuni attributi dal cloud verso Active Directory: stato dell'archivio, elenchi mittenti, deleghe pubbliche e indirizzi creati nel cloud. L'effetto si vede — su un oggetto sincronizzato compare un indirizzo X500 con prefisso /o=ExchangeLabs, nato nel cloud e tornato indietro. Non va rimosso.
Dopo l'attivazione, ciclo completo obbligatorio:
Start-ADSyncSyncCycle -PolicyType Initial#La regola di sincronizzazione personalizzata
Sincronizza solo chi ha employeeType = Interno, oppure chi porta extensionAttribute1 = SYNC365. Scrive cloudFiltered, l'attributo con cui Entra Connect decide se un oggetto esiste per il tenant.
Apertura dell'editor:
& "C:\Program Files\Microsoft Azure AD Sync\UIShell\SyncRulesEditor.exe"| Voce | Valore |
|---|---|
| Direzione | Inbound |
| Sistema connesso | contoso.lab |
| Tipo oggetto | user → person |
| Tipo di collegamento | Join |
| Precedenza | 50 |
| Attributo di destinazione | cloudFiltered |
| Tipo di flusso | Expression |
IIF(IsPresent([employeeType]),
IIF([employeeType]="Interno", False,
IIF(IsPresent([extensionAttribute1]),
IIF([extensionAttribute1]="SYNC365", False, True), True)),
IIF(IsPresent([extensionAttribute1]),
IIF([extensionAttribute1]="SYNC365", False, True), True))employeeType | extensionAttribute1 | cloudFiltered | Esito |
|---|---|---|---|
Interno | qualsiasi | False | sincronizza |
| altro | SYNC365 | False | sincronizza |
| altro | altro o assente | True | escluso |
| assente | SYNC365 | False | sincronizza |
| assente | assente | True | escluso |
Quattro cose da sapere prima di scriverla. L'espressione va nel campo Source: non esiste un campo separato, e quando si imposta FlowType = Expression la colonna Source diventa una casella di testo libera — è il punto in cui ci si blocca cercando un campo che non c'è. La precedenza deve stare sotto 100, perché da 100 in su ci sono le regole Microsoft e una regola sopravanzata non scrive mai cloudFiltered. Niente operatori booleani: il linguaggio è limitato, e IsPresent va verificato prima di confrontare un attributo che potrebbe mancare. E dopo ogni modifica serve un ciclo Initial, perché un Delta valuta solo gli oggetti cambiati di recente.
#Modificare le regole in sicurezza
Vale da qui in avanti, per sempre.
- Attivare la modalità staging
- Modificare o creare la regola nell'editor
- Eseguire un ciclo
Initial - Ispezionare le operazioni in attesa (Pending Export)
- Correggere finché non compaiono eliminazioni indesiderate
- Solo allora disattivare lo staging
- Eseguire un altro ciclo
Initial - Verificare gli oggetti nel tenant
Il passo 5 è quello che protegge davvero. In staging il motore importa, applica le regole e calcola tutte le differenze ma non esporta nulla: si può sbagliare quante volte serve.
#Quando un utente non si sincronizza
Lo strumento che risponde in trenta secondi:
& "C:\Program Files\Microsoft Azure AD Sync\UIShell\miisclient.exe"Connectors → connettore della foresta → Search Connector Space → Scope: DN → il nome distinto dell'oggetto → Preview → Generate Preview → Import Attribute Flow. Lì si vede ogni regola applicata, in ordine di precedenza, e il valore finale di ogni attributo.
Parte 9 — Verifica finale e uso quotidiano
#L'ordine di accensione
L'ordine conta: ogni macchina dipende dalle precedenti. E poiché la memoria non basta per tutte, si accende solo quello che serve.
qm start 1310 # firewall — la rete deve esistere prima di tutto
sleep 60
qm start 1300 # domain controller — autenticazione e DNS
sleep 90
qm start 1301 ; qm start 1302 # i due Exchange
sleep 120
qm start 1311 # sincronizzazione (facoltativa)
qm start 1305 # perimetro (facoltativa)
qm start 1303 # bilanciatore (facoltativa)
qm start 1304 # client (facoltativa)Spegnimento in ordine inverso, verificando prima che le code siano vuote.
#La lista di controllo
Dopo ogni accensione, e dopo ogni manutenzione:
Get-ClusterNode | ft Name,State -Auto
Get-ClusterResource | ft Name,State -Auto # il testimone deve essere Online
Get-MailboxDatabaseCopyStatus * | ft Name,Status,ActiveCopy -Auto
Test-ReplicationHealth -Identity LAB-MBX01 | Where-Object Result -ne 'Passed'
Get-Queue -Server LAB-MBX01 | ft Identity,Status,MessageCount -Auto| Cosa | Atteso |
|---|---|
| Nodi cluster | Entrambi Up |
| Testimone | Online |
| Copie database | Due Mounted per nodo, le altre Healthy |
| Salute replica | Nessuna riga in uscita |
| Code | Vuote, tranne Shadow |
Rimedio:
Start-ClusterGroup "Cluster Group"#Portare un utente fino al cloud
La prova che tutto funziona insieme.
# 1 — sul domain controller: creare l'account nell'unità sincronizzata
New-ADUser -Name "mario.rossi" -DisplayName "Mario Rossi" `
-SamAccountName "mario.rossi" -UserPrincipalName "[email protected]" `
-Path "OU=SYNC,DC=contoso,DC=lab" -OtherAttributes @{employeeType="Interno"} `
-AccountPassword (Read-Host -AsSecureString "Password") -Enabled $true
# 2 — sul server cassette: la casella
Enable-Mailbox -Identity mario.rossi -Database DB01
Set-Mailbox mario.rossi -EmailAddressPolicyEnabled $false `
-PrimarySmtpAddress [email protected]
# 3 — sul server di sincronizzazione
Start-ADSyncSyncCycle -PolicyType Delta
# 4 — verificare nel tenant
Connect-MgGraph -Scopes User.Read.All -NoWelcome
Get-MgUser -All -Property UserPrincipalName,OnPremisesSyncEnabled |
Where-Object { $_.UserPrincipalName -like "*@lab.impicciando.it" } |
ft UserPrincipalName,OnPremisesSyncEnabled -AutoSizeSe l'utente non compare, le cause sono due e si controllano in quest'ordine: non è in OU=SYNC, oppure non supera il filtro della regola.
#Quando qualcosa non torna
Tre principi, in ordine di utilità.
Leggere il log, non il messaggio a schermo. Il messaggio della finestra è quasi sempre generico; la causa vera sta nel registro eventi.
Confrontare due misure indipendenti. Un solo dato non dice se è giusto. Due dati che dovrebbero coincidere e non coincidono individuano il problema.
Distinguere il livello che fallisce. TCP che connette ma LDAP che non risponde significa che il problema è sopra il trasporto, non nella rete.
I quattro guasti che hanno richiesto una diagnosi vera durante la costruzione sono raccontati per esteso: l'orologio, il connettore IPv6, i presupposti degli script e l'indirizzo primario. In tutti e quattro il messaggio d'errore indicava una direzione sbagliata.
Per il ragionamento dietro ogni scelta di progetto: il manuale del laboratorio.
Nessun risultato. Prova con un componente (DAG, pfSense), un cmdlet (New-MailboxDatabase) o una fase (schema, sottoscrizione, staging).