Hi Serge Caron,
How was the issue? It seems your previous answer has been deleted. I could not read it.
VP
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
In order to reduce complexity, I am using a test domain consisting of a single domain controller ON PREMISES and a single user, the domain administrator.
There is a single role installed: Remote Desktop Gateway. None of the other 5 RDS roles are installed. Direct Access and/or VPNs are not allowed in this test.
The AD domain is MyDomain.local and the internal FQDN is MyServer.MyDomain.local
There is a Let's Encrypt certificate installed on this server for MyServer.MyDomain.TLD.
Finally, there is a single port 443 forwarded to the server from the firewall.
I can RDP into this server from the Internet to MyServer.MyDomain.local via RDG MyServer.MyDomain.TLD.
However, all logons are downgraded to NTLMv2 even if I set
rdgiskdcproxy:i:1
kdcproxyname:s:MyServer.Mydomain.TLD
In this test case, I am using a non domain joined Windows 10 Pro client (even if I know it is deprecated): we need to demonstrate RDG working with BYOD, including non Windows devices.
Is there a way to do this in Windows Server 2025 ?
Hi Serge Caron,
How was the issue? It seems your previous answer has been deleted. I could not read it.
VP
Hello VPHAN,
I have more control over the Server 2022 so I will concentrate on this server first.
There is some use case on this server for SSTP but not when using IPv6. So, the binding was removed. By the way, I did add some warnings in my script to alert for this condition.
I have also updated the server to the latest build (just in case...).
Finally, I have enabled the Kerberos-KDCProxy event log.
When a connection is attempted, I see events 400 ("An HTTP request was recived"), an event 306 ("Rediscover the KDC for domain mydomain.tld" notice the case: it matches the domain's UPN), an event 309 (KDC ...IP... (\FQDN of the DC) rediscovered for domain mydomain.tld/MYDOMAIN.TLD depending on UPN).
This sequence is repeated three times as if the explicit KDC mapping is ignored by the RDG.
On this domain, there is a single DC and "dcdiag /test:CheckSecurityError" sucessfully passes all tests.
All internal RDP sessions display the padlock "Identify Verified by Kerberos".
The RDG server does not log the NTLM or Kerberos events, which is an annoyance considering event 400 quoted above.
The server is presently at 21H2 Build 20348.4529 in case this is significant.
There is a piece missing :-(
Regards,
To answer your question directly: Yes, this is a conflict between two applications, and it is the primary cause of your issue.
The presence of the AppID {ba195980-cd49-458b-9e23-c84ee0adcd75} on the IPv6 binding [::]:443 is fatal for RD Gateway connections originating from IPv6-enabled clients (which almost all modern Windows clients are). To explain:
AppID {4dc3e181-e14b-4a21-b022-59fc669b0914}: This is IIS (Internet Information Services). RD Gateway and the KDC Proxy run on top of IIS. They rely on IIS to receive the traffic, parse the /remoteDesktopGateway/ or /KdcProxy URL, and route it to the correct application pool.
AppID {ba195980-cd49-458b-9e23-c84ee0adcd75}: This is the AppID for SSTP (Secure Socket Tunneling Protocol), a component of the Routing and Remote Access Service (RAS).
HTTP.sys (the kernel-mode driver handling port 443) uses IP Listen Lists and AppID bindings to decide which application stack "owns" a packet. When you have a specific binding for [::]:443 assigned to the SSTP AppID, and a generic binding for 0.0.0.0:443 assigned to the IIS AppID:
Your client connects via IPv6 (preferred by Windows). HTTP.sys matches the traffic to the specific [::]:443 listener. It hands the traffic to the SSTP stack. SSTP does not know what /remoteDesktopGateway/ or /KdcProxy is. It expects a VPN handshake. The request fails or times out. The client, unable to establish the HTTP transport for the Proxy, assumes the Gateway is unreachable or incapable of that auth method, and silently downgrades or fails the specific Kerberos Pre-Auth leg.
You mentioned you "updated" the faulty association. If you used netsh http update sslcert, you likely only updated the Certificate Hash but left the AppID as {ba19...}. This means SSTP still owns the port, just with a new valid certificate. The traffic is still going to the wrong destination.
=> So the solution is to delete the Rogue Binding
You do not need to "update" the IPv6 binding to point to IIS. Typically, if you remove the specific IPv6 override, IIS (which is bound to 0.0.0.0:443 but usually listens on the "Any" interface ::) will automatically pick up the traffic. If strict inclusion is required, you must add it with the IIS AppID.
On both Production Servers (Server 2022 and Server 2025), execute the following:
Delete the conflicting IPv6 binding: netsh http delete sslcert ipport=[::]:443
Verify IIS pickup: Restart the World Wide Web Publishing Service (W3SVC). Run netsh http show sslcert.
Scenario A: You only see 0.0.0.0:443. Test connectivity. IIS often handles IPv6 traffic via the IPv4 listen socket in dual-stack mode.
Scenario B: If you need an explicit IPv6 binding (common on hardened servers), add it back using the IIS AppID:
netsh http add sslcert ipport=[::]:443 certhash=<YOUR_THUMBPRINT> appid={4dc3e181-e14b-4a21-b022-59fc669b0914} certstorename=MY
Regarding the Server 2022 "Realm\UPN" Issue, once you fix the binding above, the Server 2022 RDG should start receiving the KDC Proxy traffic correctly. However, if after fixing the binding you still see the realm\UPN issue: Ensure your script's logic for the KdcProxy registry key uses the exact casing expected by the client. In your script:
$DomainName = (Get-WmiObject Win32_ComputerSystem).Domain.ToUpper()
If your client sends the realm as MYDOMAIN.LOCAL but the registry key is created as mydomain.local (or vice versa), KpsSvc should be case-insensitive, but I have seen edge cases where it matters. Since you enforce .ToUpper() in the script, you are likely safe, but verify that the Key Name in the registry matches exactly what klist shows on the client side.
I hope you've found something useful here. If it helps you get more insight into the issue, it's appreciated to accept the answer. Should you have more questions, feel free to leave a message. Have a nice day!
VP
OOPS! I forgot to note the addition regarding non-DC RDGs.
Immediately after the "If ($RDSRole.Installed) {" statement, I added the code below.
The behavior of the Server 2022 RDG stays the same, wheter I just restart the kdssvc service or do a full system restart.
Regards,
### Explicit KDC Mapping (Critical for Non-DC Gateways)
Try { Get-ADDomainController -Identity $RDG -ErrorAction Stop | Out-Null }
Catch {
$DomainName = (Get-WmiObject Win32_ComputerSystem).Domain.ToUpper()
$KdcProxyPath = "HKLM:\SYSTEM\CurrentControlSet\Services\KPSSVC\Settings\KdcProxy\$DomainName"
If (-not (Test-Path $KdcProxyPath)) { New-Item -Path $KdcProxyPath -Force | Out-Null }
# Get actual DCs for the domain
$DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
New-ItemProperty -Path $KdcProxyPath -Name "KdcNames" -Type MultiString -Value $DCs -Force
}
OOPS! I forgot to note the addition regarding non-DC RDGs.
Immediately after the "If ($RDSRole.Installed) {" statement, I added the code below.
The behavior of the Server 2022 RDG stays the same, wheter I just restart the kdssvc service or do a full system restart.
Regards,
### Explicit KDC Mapping (Critical for Non-DC Gateways)
Try { Get-ADDomainController -Identity $RDG -ErrorAction Stop | Out-Null }
Catch {
$DomainName = (Get-WmiObject Win32_ComputerSystem).Domain.ToUpper()
$KdcProxyPath = "HKLM:\SYSTEM\CurrentControlSet\Services\KPSSVC\Settings\KdcProxy\$DomainName"
If (-not (Test-Path $KdcProxyPath)) { New-Item -Path $KdcProxyPath -Force | Out-Null }
# Get actual DCs for the domain
$DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
New-ItemProperty -Path $KdcProxyPath -Name "KdcNames" -Type MultiString -Value $DCs -Force
}