EN

Guida di costruzione

Costruire il laboratorioDa una macchina virtuale vuota a un Exchange ibrido

Exchange 2019 CU15 · Windows Server 2022 · Proxmox VE 9 · pfSense 2.8
10 parti · 52 sezioni · 61 blocchi comando
Revisione agosto 2026
/ per cercare

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

L1

VoceRequisitoNote
HypervisorProxmox VE 9 su una macchina fisica28 thread, 32 GB di RAM, disco SSD
Memoria38 GB a pieno regimePiù di quella fisica: si lavora a gruppi di accensione
ISOWindows Server 2022, Exchange 2019 CU15, pfSense CE 2.8, Debian 13 netinst
LicenzeNessuna: Exchange in Standard Evaluation, Windows in valutazione180 giorni
TenantUn tenant Microsoft 365 con un dominio pubblico verificatoServe un sottodominio verificabile
TempoUna settimana di sere, non un pomeriggioLa parte lunga è l'ibrido

#L'ordine dei passi, e perché è quello

L1

Ogni fase esiste perché la successiva la richiede. Saltare avanti fa fallire cose che sembrano non c'entrare.

  1. Rete — prima di tutto. Senza segmenti e instradamento, nessuna macchina si parla.
  2. Dominio — autenticazione e DNS. Exchange non si installa senza.
  3. Identità — le unità organizzative e gli attributi vanno decisi prima di creare utenti, perché spostarli dopo genera eliminazioni nel cloud.
  4. Exchange — schema, primo server, secondo server, database, DAG.
  5. Perimetro — richiede Exchange installato e l'ora allineata.
  6. Bilanciatore — richiede i due Exchange in ascolto.
  7. Ibrido — richiede tutto il resto, più un dominio verificato.

#Le convenzioni, decise una volta

L1

Vanno stabilite prima del primo comando, perché entrano in ogni nome.

ConvenzioneValorePerché
Prefisso macchineLAB-Riconoscibile a colpo d'occhio nell'elenco dell'hypervisor
Identificativi13001399Intervallo riservato, non collide con altro
PoolLAB-EXCHANGEPermessi e gestione in blocco
Dominio internocontoso.labDi fantasia: un comando di laboratorio non può colpire la produzione
NetBIOSCONTOSO
CN degli oggettiuguale al samAccountNameNon è estetica: metà dei permessi dipende da questo

Parte 1 — L'host e le macchine

#Il dimensionamento completo

L2

Questa è la tabella da cui parte tutto. I valori sono quelli in esercizio.

IDNomeRuoloBridgeIndirizzoRAMDisco
1300LAB-DC01AD DS, DNS, testimone DAGvmbr9010.20.10.10/243 GB60 GB
1301LAB-MBX01Exchange, nodo DAGvmbr9110.20.20.10/298 GB80 GB
1302LAB-MBX02Exchange, nodo DAGvmbr9110.20.20.11/298 GB80 GB
1303LAB-LB01HAProxyvmbr9210.20.20.34/29 + VIP .382 GB20 GB
1304LAB-CLI01Client applicativovmbr9310.20.30.10/244 GB60 GB
1305LAB-EDG01Trasporto perimetralevmbr9410.20.40.2/294 GB80 GB
1306LAB-EDG02Trasporto perimetralevmbr9410.20.40.3/294 GB80 GB
1310LAB-FW01Firewall pfSensetuttivedi Parte 21 GB16 GB
1311LAB-SYNC01Entra Connectvmbr9010.20.10.40/244 GB60 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

L2

Cinque bridge Linux senza porte fisiche. È l'isolamento per costruzione: un bridge senza porte non ha alcun percorso verso le schede dell'host.

BridgePorteSegmento
vmbr0scheda fisicaRete esistente — unico contatto con l'esterno
vmbr90nessunaGestione
vmbr91nessunaExchange
vmbr92nessunaBilanciatore
vmbr93nessunaClient
vmbr94nessunaDMZ

Creazione, in /etc/network/interfaces sull'host:

auto vmbr90
iface vmbr90 inet manual
    bridge-ports none
    bridge-stp off
    bridge-fd 0

Ripetere per vmbr91vmbr94, 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

L2

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 0 disattiva 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 win11 con q35 e UEFI è la combinazione che funziona con Server 2022.

#I limiti di CPU e memoria

L2

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

L1

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

L2

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

L2

Nella console, poi da interfaccia web: Interfaces → Assignments, una riga per scheda.

InterfacciaSchedaBridgeIndirizzo
DCvtnet0vmbr9010.20.10.1/24
EXCHvtnet1vmbr9110.20.20.9/29
LBvtnet2vmbr9210.20.20.33/29
CLIENTvtnet3vmbr9310.20.30.1/24
WANvtnet4vmbr0DHCP dalla rete esistente
DMZvtnet5vmbr9410.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

L2

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

L2

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

L1

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.1

Il 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

L2

Installato Windows Server 2022 con esperienza desktop, prima di promuovere:

Set-TimeZone -Id "W. Europe Standard Time"
Rename-Computer -NewName LAB-DC01 -Restart

Al 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

L2

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 -Force

Un 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

L2

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 -PassThru

I 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

L1

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:$false

Consente 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

L2

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 UPNSuffixes

contoso.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

L2

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 DC

Sul 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 /status

Sui 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

L1

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 -Force

Parte 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

L2

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

L2

Due attributi guidano tutto il resto. Vanno scelti ora perché entrano nella regola di sincronizzazione e negli script.

AttributoVista ExchangeValoriUso
employeeTypeInterno, Consulente, Funzione, EsternoDiscrimina i rami del provisioning e la regola di sincronizzazione
extensionAttribute1CustomAttribute1SYNC365, NOSYNCAmmette alla sincronizzazione chi non è Interno
extensionAttribute2CustomAttribute2dataCessazione, 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

L1

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 $true

Il 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

L3

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 `
  -Restart

Più 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

L3

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 /PrepareAllDomains

Verifica dell'avvenuta estensione:

Get-ADObject "CN=ms-Exch-Schema-Version-Pt,CN=Schema,CN=Configuration,DC=contoso,DC=lab" -Properties rangeUpper |
  Select-Object rangeUpper

Il nome dell'organizzazione entra nel gruppo amministrativo e non si cambia più.

#Installare i due server cassette

L3

.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF `
  /Mode:Install /Role:Mailbox /InstallWindowsComponents

Poi lo stesso sul secondo server.

#Il vincolo dei cinque database

L2

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 5

L'architettura che sta nel limite e dà un DAG bilanciato è quattro database con copia incrociata: quattro oggetti per server.

DatabaseAttivo suCopia suPreferenza
DB01LAB-MBX01LAB-MBX02MBX01 = 1
DB02LAB-MBX02LAB-MBX01MBX02 = 1
DB03LAB-MBX01LAB-MBX02MBX01 = 1
DB04LAB-MBX02LAB-MBX01MBX02 = 1

#Creare i database

L2

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

L3

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

L2

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 2

Il seeding parte da solo. Verifica:

Get-MailboxDatabaseCopyStatus * |
  ft Name,Status,ActiveCopy,CopyQueueLength,ReplayQueueLength,ContentIndexState -Auto

Mounted è 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

L2

DominioTipoScopo
contoso.labAuthoritativeDominio interno, predefinito
lab.impicciando.itAuthoritativeDominio verificato nel tenant — indirizzi primari
<tenant>.mail.onmicrosoft.comInternalRelayInstradamento 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 InternalRelay

Il 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

L2

Su ogni oggetto gestito dagli script.

Set-Mailbox <utente> -EmailAddressPolicyEnabled $false `
  -PrimarySmtpAddress <utente>@lab.impicciando.it

Finché 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

L2

Due connettori riproducono la coppia tipica della produzione, e la differenza spiega due errori distinti.

AnonimoAutenticato
Porta25587
PermessiAnonymousUsers + relay esplicitoExchangeUsers
ControlloSolo indirizzo sorgenteIndirizzo e credenziali
Errore tipico550 se l'indirizzo non è in elenco530 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 Verbose

Parte 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

L3

Dopo l'installazione del sistema, senza aggiunta al dominio:

Set-TimeZone -Id "W. Europe Standard Time"
Rename-Computer -NewName LAB-EDG01 -Restart

Ora, 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 Private

Orario, 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

L3

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-Computer

Una 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).HostName

Deve rispondere lab-edg01.contoso.lab. Se risponde LAB-EDG01, la sottoscrizione fallirà.

#Installare il ruolo

L3

Install-WindowsFeature ADLDS

Più i redistributable Visual C++ 2012 e 2013 x64. Poi:

.\Setup.exe /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /Mode:Install /Role:EdgeTransport

#La sottoscrizione EdgeSync

L3

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: /delete

Sul 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

L2

StatoSignificato
SyncStatus : Normal, CookieRecords > 0Funzionante
SyncStatus : Inconclusive, tutti NotStartedAtteso subito dopo la creazione: il primo ciclo non è ancora avvenuto
CouldNotConnect, The LDAP server is unavailableOrario, oppure servizi da riavviare
Stati SkippedNormale: 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 MSExchangeTransport

Verifica della porta, dal server cassette:

Test-NetConnection 10.20.40.2 -Port 50636 -InformationLevel Quiet

#La prova del percorso

L1

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 -Auto

Ripetuta 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

L3

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.248
ifup ens18:0
ip -br a        # ens18 deve mostrare sia .34 sia .38

#HAProxy

L2

apt install haproxy

Configurazione 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 check

mode 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

L1

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 25

L'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

L2

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

L3

Sulla macchina dedicata, nel dominio, con IE Enhanced Security già disattivata.

Nella procedura guidata, in modalità personalizzata:

PassoScelta
Metodo di accessoPassword Hash Synchronization
Single sign-onDisattivato
Forestacontoso.lab, con un account amministrativo
FiltroSolo OU=SYNC
AncoraggiomS-DS-ConsistencyGuid
Funzioni opzionaliPassword Hash Sync, Exchange hybrid deployment
AvvioLasciare spuntato Start the synchronization process
Import-Module ADSync
Get-ADSyncScheduler | fl SyncCycleEnabled,StagingModeEnabled,NextSyncCycleStartTimeInUTC
Set-ADSyncScheduler -SyncCycleEnabled $true

Il 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

L2

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

L3

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"
VoceValore
DirezioneInbound
Sistema connessocontoso.lab
Tipo oggettouserperson
Tipo di collegamentoJoin
Precedenza50
Attributo di destinazionecloudFiltered
Tipo di flussoExpression
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))
employeeTypeextensionAttribute1cloudFilteredEsito
InternoqualsiasiFalsesincronizza
altroSYNC365Falsesincronizza
altroaltro o assenteTrueescluso
assenteSYNC365Falsesincronizza
assenteassenteTrueescluso

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

L2

Vale da qui in avanti, per sempre.

  1. Attivare la modalità staging
  2. Modificare o creare la regola nell'editor
  3. Eseguire un ciclo Initial
  4. Ispezionare le operazioni in attesa (Pending Export)
  5. Correggere finché non compaiono eliminazioni indesiderate
  6. Solo allora disattivare lo staging
  7. Eseguire un altro ciclo Initial
  8. 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

L2

Lo strumento che risponde in trenta secondi:

& "C:\Program Files\Microsoft Azure AD Sync\UIShell\miisclient.exe"

Connectors → connettore della foresta → Search Connector SpaceScope: DN → il nome distinto dell'oggetto → PreviewGenerate PreviewImport 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

L1

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

L1

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
CosaAtteso
Nodi clusterEntrambi Up
TestimoneOnline
Copie databaseDue Mounted per nodo, le altre Healthy
Salute replicaNessuna riga in uscita
CodeVuote, tranne Shadow

Rimedio:

Start-ClusterGroup "Cluster Group"

#Portare un utente fino al cloud

L1

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 -AutoSize

Se 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

L1

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).

Sequenza di costruzione di un laboratorio Exchange 2019 ibrido realmente in funzione. Nomi, indirizzi e domini interni sono sostituiti con valori generici. Laboratorio costruito con il supporto di Ilie.