The administration and maintenance of Microsoft Exchange Server to ensure secure, reliable, and efficient email and collaboration services across an organization.
I hit exactly this error on every hybrid move (Exchange 2016 CU23, classic hybrid; MRSProxy enabled, Windows auth with Negotiate + NTLM, Extended Protection off, no NTLM restrictions, no SSL offloading, Test-MigrationServerAvailability succeeding, endpoint and batches recreated many times). Here is the full troubleshooting path that found the cause, and the workaround.
TL;DR
Exchange Online MRS mishandles migration endpoint credentials stored as DOMAIN\user: the MRS data connection authenticates with the username only and an empty NTLM domain, so the logon fails with a 401. Storing the credentials in UPN format ([email protected]) fixed it immediately.
Step 1: Turn on logon auditing (on-prem Exchange server)
auditpol /get /subcategory:"Logon"
It should show Success and Failure. If not:
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
Step 2: Get the MRS server name for each move (Exchange Online PowerShell)
Each move report names the MRS server handling it. When that server connects to your Exchange server, Windows logs it as the Workstation Name in logon events 4624 (success) / 4625 (failure). Report times are UTC.
foreach ($s in Get-MoveRequest | Get-MoveRequestStatistics -IncludeReport) {
$e = $s.Report.Entries
$m = $e | Where-Object { "$($_.Message)" -match "Mailbox Replication service '" } | Select-Object -Last 1
$srv = if ("$($m.Message)" -match "service '([^'.]+)") { $Matches[1] } else { "" }
$err = $e | Where-Object { "$($_.Message)" -like "Transient error*" } | Select-Object -Last 1
[pscustomobject]@{ User = $s.DisplayName; Status = "$($s.StatusDetail)"; MRS = $srv; LastError = ("$($err.Message)" -replace '^Transient error (\w+).*','$1'); ErrorTime = $err.CreationTime }
}
Step 3: Watch NTLM logons from Exchange Online (on-prem, elevated PowerShell)
Keep this running during the following steps (Ctrl+C to stop):
while ($true) {
Clear-Host
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624,4625; StartTime=(Get-Date).AddMinutes(-15)} -ErrorAction SilentlyContinue |
Where-Object { $_.Message -match 'NtLmSsp' } | Sort-Object TimeCreated |
ForEach-Object {
$x = [xml]$_.ToXml(); $d = @{}
$x.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
[pscustomobject]@{ Time = $_.TimeCreated.ToString('HH:mm:ss'); Id = $_.Id; User = $d.TargetUserName; Domain = $d.TargetDomainName; Workstation = $d.WorkstationName; SourceIP = $d.IpAddress; SubStatus = $d.SubStatus }
} | Where-Object { $_.Workstation -match 'PR\d+MB\d+' } | Format-Table -AutoSize
Start-Sleep 30
}
The PR\d+MB\d+ filter keeps only Exchange Online servers. Note that successful logons (4624) show the account as resolved by Windows; only failures (4625) show exactly what the client sent.
What I saw: the same MRS server, within the same job, logged a 4624 success with the domain, then one second later a 4625 failure with an empty Account Domain and Sub Status 0xC000006A, repeating every ~31 seconds (the MRS retry interval).
Step 4: Marker test – which connection uses the stored credentials?
To prove the failing logons came from the endpoint's stored credentials, I saved the endpoint account with a deliberately distinctive domain casing, e.g. MyDoMaIn\admin (Windows ignores case, so it still works). In the IIS log (/EWS/mrsproxy.svc), the successful requests showed exactly MyDoMaIn\admin, so the endpoint credentials were correct and in use. The failing requests from the same source IP seconds later carried no domain at all.
Step 5: Fake-endpoint test – what does each stage send?
Next I created a test endpoint with deliberately fake credentials (non-existent user, fake domain, fake password; nothing real is sent and no account can be locked out), skipped verification, and started a one-user batch:
$fake = New-Object System.Management.Automation.PSCredential("FAKEDOM\mrstest", (ConvertTo-SecureString "NotARealPassword1!" -AsPlainText -Force))
New-MigrationEndpoint -ExchangeRemoteMove -Name "Test-FakeDom" -RemoteServer mail.yourdomain.com -Credentials $fake -SkipVerification
Set-Content C:\temp\mrstest.csv "EmailAddress`******@yourdomain.com"
New-MigrationBatch -Name "Test-FakeDom" -SourceEndpoint "Test-FakeDom" -TargetDeliveryDomain yourtenant.mail.onmicrosoft.com -CSVData ([IO.File]::ReadAllBytes("C:\temp\mrstest.csv")) -AutoStart
(The test user must not be in any other batch or move request; remove those first.)
The watcher showed a 4625 for mrstest with domain FAKEDOM from the MigrationService server (an xxxPR01MB… name, different from the MRS servers). So the MigrationService passes the domain correctly. As expected, the batch then stopped at validation, and no move request was created, so MRS was never reached with fake credentials.
Step 6: Replace the fake credentials with real ones in UPN format
Instead of deleting the test, I put real credentials in UPN format on the same endpoint, tested first so nothing is saved unless it works:
$cred = Get-Credential -UserName "[email protected]" -Message "Migration account (UPN)"
$test = Test-MigrationServerAvailability -ExchangeRemoteMove -RemoteServer mail.yourdomain.com -Credentials $cred
$test | Format-List Result, Message
if ($test.Result -eq 'Success') { Set-MigrationEndpoint "Test-FakeDom" -Credentials $cred }
Then I restarted the batch so it picked up the new credentials:
Stop-MigrationBatch "Test-FakeDom"
Get-MigrationBatch "Test-FakeDom" | Format-List Status
Start-MigrationBatch "Test-FakeDom"
(Wait until Status shows Stopped before starting.) Within seconds the move report showed Connected to source mailbox and then CopyingMessages. I then created one batch per remaining user on the same endpoint, and all of them connected and copied, on several different MRS servers and the same MRS build that had failed with DOMAIN\user earlier that day. So it's the credential format, not a service update.
Tip: you can't switch an existing batch to another endpoint, but you can change the credentials on an endpoint with Set-MigrationEndpoint, and its batches pick them up after a stop/start.
Reading the logon events
| What you see for the MRS server | Meaning | Fix |
|---|---|---|
4624, then 4625 with empty Account Domain, 0xC000006A, every ~31 s |
MRS drops the domain from DOMAIN\user credentials |
Use UPN credentials |
4625 with domain, 0xC000006A |
Wrong password | Update the endpoint credentials |
4625, 0xC0000064 |
Username doesn't exist | Check the account name |
4625, 0xC0000234 |
Account locked out | Unlock it, then fix the stored credentials |
No events at all, move shows TimeoutErrorTransientException |
Traffic never reaches your server | Check your firewall (below) |
If you see timeouts instead of 401s
Check your firewall for geo-blocking. Some Exchange Online addresses geolocate to the US even when the server is in another region (in my case an Australian MRS server with an address inside the published range 40.104.0.0/15). My firewall dropped them, so the move just timed out and left no trace on the Exchange server. Allow the published Exchange Online ranges (endpoint ID 1 for 443, ID 10 for 25) ahead of any region blocking. On UniFi, "Detection Exclusions" only apply to IDS/IPS, not to Region Blocking, so you need a firewall allow rule placed above the region block.
Clean-up afterwards
Once the moves complete, remove the test batch and endpoint (Remove-MigrationBatch, Remove-MigrationEndpoint), and keep UPN credentials on your main endpoint for future moves.