I did this
Background
We're using git on Windows to connect to our on-prem Microsoft Azure DevOps Server using Windows Integrated Authentication. Clients are Windows 11 25H2, server is Windows Server 2022 running IIS10. The server endpoint is configured for Kerberos via Negotiate, with Extended Protection for Authentication (EPA) set to Required as per MS security hardening guidance. Microsoft have publicised a removal of NTLM support from git in this scenario (https://devblogs.microsoft.com/devops/upcoming-change-ntlm-removal-in-git-libcurl-impact-to-azure-devops-server-customers), and we are testing to see what work is needed to deal with that change. It seems that the only reason our git clients have been successfully negotiating WIA against our DevOps Server is because they've been silently falling back to NTLM, and that if NTLM support is removed from (or disabled in) git they try to negotiate Kerberos but fail. Normal browsers (Chrome and Edge) are happily negotiating WIA using Kerberos against the same Azure DevOps Server URLs (confirmed Kerberos rather than NTLM, it's only git that's failing).
The simple test below removes git from the equation and just focusses on a modern build of curl - curl 8.21.0 (x86_64-w64-mingw32) - which is included with Portable Git 2.55.0.3 x64. It's targeting a test URL (https://wiatest.mydomain.local/default.html) on a Windows Server 2022 server setup with the same config as our production Azure DevOps Server - IIS10, Windows Integrated Authentication enabled, Extended Protection for Authentication set to Required.
Simple test
Logged into Win 11 25H2 workstation joined to the same AD domain as the target IIS server hosting this endpoint. Purged all Kerberos tokens with klist purge, execute curl:
"D:\Downloads\PortableGit-2.55.0.3-64-bit.7z\mingw64\bin\curl.exe" -v --negotiate -u : "https://wiatest.mydomain.local/default.html"
Outcome
Instead of the expected successful HTTP 200 OK response I get a 401 Unauthorized like so:
* Host wiatest.mydomain.local:443 was resolved.
* IPv6: (none)
* IPv4: 10.yyy.yyy.yyy
* Trying 10.yyy.yyy.yyy:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* Established connection to wiatest.mydomain.local (10.yyy.yyy.yyy port 443) from 10.xxx.xxx.xxx port 60067
* using HTTP/1.x
* Server auth using Negotiate with user ''
> GET /default.html HTTP/1.1
> Host: wiatest.mydomain.local
> Authorization: Negotiate YIIPggYGKwYBBQU
[SNIP FULL KERBEROS TOKEN]
> User-Agent: curl/8.21.0
> Accept: */*
>
* Request completely sent off
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
< HTTP/1.1 401 Unauthorized
< Content-Type: text/html
< Server: Microsoft-IIS/10.0
< WWW-Authenticate: Negotiate
< WWW-Authenticate: NTLM
< Date: Fri, 17 Jul 2026 05:00:16 GMT
< Content-Length: 1293
<
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>401 - Unauthorized: Access is denied due to invalid credentials.</h2>
<h3>You do not have permission to view this directory or page using the credentials that you supplied.</h3>
</fieldset></div>
</div>
</body>
</html>
* Connection #0 to host wiatest.mydomain.local:443 left intact
After the failure I can see that the client machine successfully got a new Kerberos ticket for the correct hostname (wiatest.mydomain.local), yet it still gets the 401 Unauthorized response. Looking in the Security eventlog on the server I can see a corresponding failed Kerberos logon like so:
Log Name: Security
Source: Microsoft-Windows-Security-Auditing
Date: 17/07/2026 5:00:16 AM
Event ID: 4625
Task Category: Logon
Level: Information
Keywords: Audit Failure
User: N/A
Computer: APP02.mydomain.local
Description:
An account failed to log on.
Subject:
Security ID: NULL SID
Account Name: -
Account Domain: -
Logon ID: 0x0
Logon Type: 3
Account For Which Logon Failed:
Security ID: NULL SID
Account Name:
Account Domain: -
Failure Information:
Failure Reason: An Error occured during Logon.
Status: 0xC000035B
Sub Status: 0x0
Process Information:
Caller Process ID: 0x0
Caller Process Name: -
Network Information:
Workstation Name: -
Source Network Address: 10.xxx.xxx.xxx
Source Port: 60067
Detailed Authentication Information:
Logon Process: Kerberos
Authentication Package: Kerberos
Transited Services: -
Package Name (NTLM only): -
Key Length: 0
If I do a simple equivalent test using a WinHTTP client from the same machine like so I get a successful HTTP 200 OK response, with WIA using Kerberos succeeding:
Invoke-WebRequest -Uri 'https://wiatest.mydomain.local/default.html' -UseDefaultCredentials -UseBasicParsing
Changing EPA from Required to Allowed on the server endpoint doesn't help, the curl test still fails in exactly the same way. If I disable EPA entirely however - setting it to Disabled on the server - the curl test succeeds.
I expected the following
* Host wiatest.mydomain.local:443 was resolved.
* IPv6: (none)
* IPv4: 10.yyy.yyy.yyy
* Trying 10.yyy.yyy.yyy:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* Established connection to wiatest.mydomain.local (10.yyy.yyy.yyy port 443) from 10.xxx.xxx.xxx port 54438
* using HTTP/1.x
* Server auth using Negotiate with user ''
> GET /default.html HTTP/1.1
> Host: wiatest.mydomain.local
> Authorization: Negotiate YIIPggYGKwYBBQUCoIIPdjCC
[SNIP FULL KERBEROS TOKEN]
> User-Agent: curl/8.21.0
> Accept: */*
>
* Request completely sent off
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
< HTTP/1.1 200 OK
< Content-Type: text/html
< Last-Modified: Fri, 17 Jul 2026 04:26:57 GMT
< Accept-Ranges: bytes
< ETag: "fb75881a415dd1:0"
< Server: Microsoft-IIS/10.0
* Negotiate: noauthpersist -> 0, header part: true
< Persistent-Auth: true
< WWW-Authenticate: Negotiate oYG3MIG0oAMKAQ
[SNIP FULL KERBEROS TOKEN]
< Date: Fri, 17 Jul 2026 05:13:29 GMT
< Content-Length: 84
<
<html>
<head>Default Document</head>
<body>
<p>Hello World!</p>
</body>
</html>* Connection #0 to host wiatest.mydomain.local:443 left intact
curl/libcurl version
curl 8.21.0 (x86_64-w64-mingw32) libcurl/8.21.0 Schannel zlib/1.3.2 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 WinLDAP
Release-Date: 2026-06-24
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp scp sftp smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL SSPI threadsafe UnixSockets zstd
operating system
Client: Windows 11 25H2 (26200.8457)
Server: Windows Server 2022 (20348.5020)
I did this
Background
We're using git on Windows to connect to our on-prem Microsoft Azure DevOps Server using Windows Integrated Authentication. Clients are Windows 11 25H2, server is Windows Server 2022 running IIS10. The server endpoint is configured for Kerberos via Negotiate, with Extended Protection for Authentication (EPA) set to Required as per MS security hardening guidance. Microsoft have publicised a removal of NTLM support from git in this scenario (https://devblogs.microsoft.com/devops/upcoming-change-ntlm-removal-in-git-libcurl-impact-to-azure-devops-server-customers), and we are testing to see what work is needed to deal with that change. It seems that the only reason our git clients have been successfully negotiating WIA against our DevOps Server is because they've been silently falling back to NTLM, and that if NTLM support is removed from (or disabled in) git they try to negotiate Kerberos but fail. Normal browsers (Chrome and Edge) are happily negotiating WIA using Kerberos against the same Azure DevOps Server URLs (confirmed Kerberos rather than NTLM, it's only git that's failing).
The simple test below removes git from the equation and just focusses on a modern build of curl - curl 8.21.0 (x86_64-w64-mingw32) - which is included with Portable Git 2.55.0.3 x64. It's targeting a test URL (https://wiatest.mydomain.local/default.html) on a Windows Server 2022 server setup with the same config as our production Azure DevOps Server - IIS10, Windows Integrated Authentication enabled, Extended Protection for Authentication set to Required.
Simple test
Logged into Win 11 25H2 workstation joined to the same AD domain as the target IIS server hosting this endpoint. Purged all Kerberos tokens with klist purge, execute curl:
"D:\Downloads\PortableGit-2.55.0.3-64-bit.7z\mingw64\bin\curl.exe" -v --negotiate -u : "https://wiatest.mydomain.local/default.html"Outcome
Instead of the expected successful HTTP 200 OK response I get a 401 Unauthorized like so:
After the failure I can see that the client machine successfully got a new Kerberos ticket for the correct hostname (wiatest.mydomain.local), yet it still gets the 401 Unauthorized response. Looking in the Security eventlog on the server I can see a corresponding failed Kerberos logon like so:
If I do a simple equivalent test using a WinHTTP client from the same machine like so I get a successful HTTP 200 OK response, with WIA using Kerberos succeeding:
Invoke-WebRequest -Uri 'https://wiatest.mydomain.local/default.html' -UseDefaultCredentials -UseBasicParsingChanging EPA from Required to Allowed on the server endpoint doesn't help, the curl test still fails in exactly the same way. If I disable EPA entirely however - setting it to Disabled on the server - the curl test succeeds.
I expected the following
curl/libcurl version
curl 8.21.0 (x86_64-w64-mingw32) libcurl/8.21.0 Schannel zlib/1.3.2 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 WinLDAP
Release-Date: 2026-06-24
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp scp sftp smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL SSPI threadsafe UnixSockets zstd
operating system
Client: Windows 11 25H2 (26200.8457)
Server: Windows Server 2022 (20348.5020)