152

Issue

I get this exception

The underlying connection was closed: An unexpected error occurred on a send.

in my logs, and it is breaking our OEM integration with our e-mail marketing system at random times. (varying from 1-4 hours)

My website is hosted on a Windows Server 2008 R2 with IIS 7.5.7600.

This website has a large number of OEM components, and comprehensive dashboard. Everything works fine with all the other elements of the website except with one of our e-mail marketing component which we are using as an iframe solution within our dashboard.

The way it works is, I send a HttpWebRequest object with all the credentials, and I get a url back which I put in an iframe and it works.

But it only works for some time (1-4 hours), and then from the call to

webRequest.GetResponse();

I get the exception

The underlying connection was closed: An unexpected error occurred on a send.

Even if the system tries to get the URL from the httpWebRequest it fails with the same exception.

The only way to make it work again is:

  • to recycle the application pool
  • anything is edited in web.config.

I am really exhausted all the option that i could think of.

Options tried

Explicitly added,

  • keep-alive = false

  • keep-alive = true

  • Increased the time out:

    <httpRuntime maxRequestLength="2097151" executionTimeout="9999999" enable="true" requestValidationMode="2.0" />

I have uploaded this page to a non SSL website to check if the SSL certificate on our production server is making the connection to drop some how.

Any direction toward resolution is greatly appreciated.

Code


    Public Function CreateHttpRequestJson(ByVal url) As String
        Try
            Dim result As String = String.Empty
            Dim httpWebRequest = DirectCast(WebRequest.Create("https://api.xxxxxxxxxxx.com/api/v3/externalsession.json"), HttpWebRequest)
            httpWebRequest.ContentType = "text/json"
            httpWebRequest.Method = "PUT"
            httpWebRequest.ContentType = "application/x-www-form-urlencoded"
            httpWebRequest.KeepAlive = False
            'ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3

            'TODO change the integratorID to the serviceproviders account Id, useremail 
            Using streamWriter = New StreamWriter(httpWebRequest.GetRequestStream())
                Dim json As String = New JavaScriptSerializer().Serialize(New With { _
                Key .Email = useremail, _
                Key .Chrome = "None", _
                Key .Url = url, _
                Key .IntegratorID = userIntegratorID, _
                Key .ClientID = clientIdGlobal _
                })

                'TODO move it to the web.config, Following API Key is holonis accounts API Key
                SetBasicAuthHeader(httpWebRequest, holonisApiKey, "")
                streamWriter.Write(json)
                streamWriter.Flush()
                streamWriter.Close()

                Dim httpResponse = DirectCast(httpWebRequest.GetResponse(), HttpWebResponse)
                Using streamReader = New StreamReader(httpResponse.GetResponseStream())
                    result = streamReader.ReadToEnd()
                    result = result.Split(New [Char]() {":"})(2)
                    result = "https:" & result.Substring(0, result.Length - 2)
                End Using
            End Using
            Me.midFrame.Attributes("src") = result
        Catch ex As Exception
            objLog.WriteLog("Error:" & ex.Message)
            If (ex.Message.ToString().Contains("Invalid Email")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Email Taken")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Invalid Access Level")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Unsafe Password")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Invalid Password")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Empty Person Name")) Then
                'TODO Show message on UI
            End If
        End Try
    End Function
  

    Public Sub SetBasicAuthHeader(ByVal request As WebRequest, ByVal userName As [String], ByVal userPassword As [String])
        Dim authInfo As String = Convert.ToString(userName) & ":" & Convert.ToString(userPassword)
        authInfo = Convert.ToBase64String(Encoding.[Default].GetBytes(authInfo))
        request.Headers("Authorization") = "Basic " & authInfo
    End Sub
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Arvind Morwal
  • 1,811
  • 2
  • 14
  • 9
  • 13
    yes I was able to get it working with this code ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls Or SecurityProtocolType.Ssl3 – Arvind Morwal Sep 02 '14 at 19:40
  • 3
    @user3458212 you should add your comment in as an answer – icc97 Jul 07 '15 at 19:59
  • 2
    In my case, running the web site in Visual Studio 15 everything goes fine, but at the end, because I cannot upgrade framework in the server, and forcing TLS 1.2 and disabling keep-alive don't work, I had to setup an intermediate web server to proxy the target web server that drops the connection. – José Roberto García Chico Apr 28 '17 at 03:54

16 Answers16

251

For me it was tls12:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
user3071284
  • 6,955
  • 6
  • 43
  • 57
Lanklaas
  • 2,870
  • 1
  • 15
  • 18
  • 54
    Note that you must be careful because this change is global to your AppDomain, and will cause calls to any site which doesn't offer TLS 1.2 to fail (which you may prefer if the data to be transported is truly sensitive). To prefer TLS 1.2 but still allow 1.1 and 1.0, you have to OR them: `ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;` – Dusty Mar 17 '17 at 17:33
  • same here, you saved my life, spent so much time to figure out what was wrong with RestSharp – darul75 Mar 31 '17 at 10:19
  • 1
    This solution also works for those not using RestSharp as well as for those that are not using ServicePointManager. Just copy and paste the above line before your WebRequest call or whatever you're using to make the request. I initially ignored this solution because of the above reasons. – goku_da_master Aug 16 '17 at 17:14
  • 5
    or just add it to what's already there... `System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;` – uosjead Sep 19 '18 at 14:30
  • 10
    To do this in PowerShell, "binary or" them together like so: `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls` – Adam S Dec 11 '18 at 17:58
  • For VB.Net, use Xor: ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 Xor SecurityProtocolType.Tls11 Xor SecurityProtocolType.Tls – DavidScherer Apr 22 '20 at 14:50
86

If you are stuck with .Net 4.0 and the target site is using TLS 1.2, you need the following line instead. ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

source: TLS 1.2 and .NET Support: How to Avoid Connection Errors

Robin Kanters
  • 5,018
  • 2
  • 20
  • 36
Tochi
  • 871
  • 6
  • 5
31

Go to your web.config/App.config to verify which .net runtime you are using

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>

Here is the solution:

  1. .NET 4.6 and above. You don’t need to do any additional work to support TLS 1.2, it’s supported by default.

  2. .NET 4.5. TLS 1.2 is supported, but it’s not a default protocol. You need to opt-in to use it. The following code will make TLS 1.2 default, make sure to execute it before making a connection to secured resource:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12

  1. .NET 4.0. TLS 1.2 is not supported, but if you have .NET 4.5 (or above) installed on the system then you still can opt in for TLS 1.2 even if your application framework doesn’t support it. The only problem is that SecurityProtocolType in .NET 4.0 doesn’t have an entry for TLS1.2, so we’d have to use a numerical representation of this enum value:

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

  1. .NET 3.5 or below. TLS 1.2 is not supported (*) and there is no workaround. Upgrade your application to more recent version of the framework.
Guo Huang
  • 923
  • 8
  • 8
  • 3
    +1 - this part of your answer is key **make sure to execute it before making a connection to secured resource:**. I had set the `SecurityProtocol` after creating my request with `var request = (HttpWebRequest)WebRequest.Create(url);`. This meant that the first request always failed, but subsequent requests were fine. Moving the set to before creating the `request` object sorted it. – Lazlow Nov 17 '20 at 13:55
29

The code below resolved the issue

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls Or SecurityProtocolType.Ssl3
th1rdey3
  • 4,176
  • 7
  • 30
  • 66
Arvind Morwal
  • 1,811
  • 2
  • 14
  • 9
  • 8
    This would work, however, keep in mind that `ServicePointManager.SecurityProtocol` is a static object which means changing this value will affect all sub-sequence `WebRequest` or `WebClient` calls. You could create separate `AppDomain` if you want `ServicePointManager` to have different settings. See http://stackoverflow.com/questions/3791629/set-the-securityprotocol-ssl3-or-tls-on-the-net-httpwebrequest-per-request for more details. – stack247 Sep 28 '15 at 19:47
  • 1
    Helpful additional reading to understand what this code is doing: http://stackoverflow.com/questions/26389899/how-do-i-disable-ssl-fallback-and-use-only-tls-for-outbound-connections-in-net/26392698 – Jon Schneider Mar 09 '16 at 19:25
  • @Marnee I put it in my application's composition root, so it is set prior to any I/O ever occuring – JG in SD Aug 04 '16 at 18:45
  • @Marnee Put it in a static constructor, so it's executed exactly once, the first time the class is accessed. I had to enable all protocols though, to cover all cases. – Nyerguds Sep 01 '16 at 17:27
  • Set this value BEFORE `WebRequest` object creation. If the object has been already created with the "old" ServicePointManager settings, it will use those, even if you have set new ones just before issuing a request to a server. – Janeks Bergs Oct 14 '20 at 18:15
17

I've been having the same issue for days now with an integration that also just "used to work before".

Out of sheer depression, I just tried

 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;

This solved it for me..even though the integration strictly only makes use of SSLv3.

I came to the realization that something if off since Fiddler reported saying that there is an "empty TLS negotiation cipher" or something of the like.

Hopefully it works!

James Skemp
  • 8,018
  • 9
  • 64
  • 107
Medismal
  • 421
  • 3
  • 18
  • Note that, even if your code doesn't need TLS, if the server it is communicating with DOES, it will try to negotiate with TLS and fail if it can't. The empty TLS negotiation cipher was the slot that was expecting the TLS protocol exchange you eventually supplied. It likely "used to work before" because the server admin likely had just enabled TLS on the server your application was communicating with. – vapcguy Apr 21 '20 at 08:37
15

In my case the site that I'm connecting to has upgraded to TLS 1.2. As a result I had to install .net 4.5.2 on my web server in order to support it.

aboy021
  • 2,096
  • 2
  • 23
  • 23
  • Can this be done at the registry level on the machine? I've disabled all SSL protocols, leaving TLS 1.0, 1.1, 1.2, however it is my understanding that anything less than TLS 1.2 should be removed soon in order to be PCI compliant. – brendo234 Jan 23 '18 at 23:42
7

Just add:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

M.Qudah
  • 87
  • 1
  • 2
6

I've found that this is a sign that the server where you're deploying code has an old .NET framework installed that doesn't support TLS 1.1 or TLS 1.2. Steps to fix:

  1. Installing the latest .NET Runtime on your production servers (IIS & SQL)
  2. Installing the latest .NET Developer Pack on your development machines.
  3. Change the "Target framework" settings in your Visual Studio projects to the latest .NET framework.

You can get the latest .NET Developer Pack and Runtime from this URL: http://getdotnet.azurewebsites.net/target-dotnet-platforms.html

Patrick Montelo
  • 2,211
  • 1
  • 20
  • 14
6

I found Solution by updating web.config with below code.

My .Net Framwork was set to 4.0 and after organization level TLS protocol update I suddenly started facing this issue. So I referred above answer of "Guo Huang" and updated the targetFramework and it worked.

<system.web>
        <compilation debug="true" targetFramework="4.7.2" />
        <httpRuntime targetFramework="4.7.2"  maxRequestLength="102400" executionTimeout="3600"/>
</system.web>
Dharman
  • 30,962
  • 25
  • 85
  • 135
Shirish
  • 61
  • 1
  • 4
  • In my case, the Exception was occurring randomly for the same code. I checked many solutions, but only this one helped - the part with `executionTimeout="3600"`. Thanks! – Wojciech X Sep 28 '22 at 17:21
5

We had this issue whereby a website that was accessing our API was getting the “The underlying connection was closed: An unexpected error occurred on a send.” message.

Their code was a mix of .NET 3.x and 2.2, which as I understand it means they are using TLS 1.0.

The answer below can help you diagnose the issue by enabling TLS 1.0, SSL 2 and SSL3, but to be very clear, you do not want to do that long-term as all three of those protocols are regarded as insecure and should no longer be used:

To get our IIS to respond to their API calls we had to add registry settings on the IIS's server to explicitly enable versions of TLS - NOTE: You have to restart the Windows server (not just the IIS service) after making these changes:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.0\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.0\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.1\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.1\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.2\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.2\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

If that doesn't do it, you could also experiment with adding the entry for SSL 2.0:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001

To be clear, this is not a nice solution, and the right solution is to get the caller to use TLS 1.2, but the above can help diagnose that this is the issue.

You can speed adding those reg entries up with this powershell script:

$ProtocolList       = @("SSL 2.0","SSL 3.0","TLS 1.0", "TLS 1.1", "TLS 1.2")
$ProtocolSubKeyList = @("Client", "Server")
$DisabledByDefault = "DisabledByDefault"
$Enabled = "Enabled"
$registryPath = "HKLM:\\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\"

foreach($Protocol in $ProtocolList)
{
    Write-Host " In 1st For loop"
        foreach($key in $ProtocolSubKeyList)
        {         
            $currentRegPath = $registryPath + $Protocol + "\" + $key
            Write-Host " Current Registry Path $currentRegPath"
            if(!(Test-Path $currentRegPath))
            {
                Write-Host "creating the registry"
                    New-Item -Path $currentRegPath -Force | out-Null             
            }
            Write-Host "Adding protocol"
                New-ItemProperty -Path $currentRegPath -Name $DisabledByDefault -Value "0" -PropertyType DWORD -Force | Out-Null
                New-ItemProperty -Path $currentRegPath -Name $Enabled -Value "1" -PropertyType DWORD -Force | Out-Null    
    }
}
 
Exit 0

That's a modified version of the script from the Microsoft help page for Set up TLS for VMM. This basics.net article was the page that originally gave me the idea to look at these settings.

tomRedox
  • 28,092
  • 24
  • 117
  • 154
  • Our issue was around a release pipeline via Team City that suddenly stopped when we changed the certificate for you live server. We had already changed the server to only use TLS1.2 and our Team City pipeline stopped working... Worked like a dream... added the reg entries and restarted the server... BOOM!!! Thanks tomRedox!! – Gwasshoppa Jan 28 '19 at 20:04
  • @Gwasshoppa, just to reiterate that the above is only a stopgap to diagnose the issue. Now you know it's a TLS version issue the solution is to change the release pipeline so that it can work with TLS1.2 and then switch off TLS <1.2 and SSL 2 and 3 again. I've updated the answer above slightly to stress that too. – tomRedox Jan 29 '19 at 07:47
3

It if helps someone, ours was an issue with missing certificate. Environment is Windows Server 2016 Standard with .Net 4.6.

There is a self hosted WCF service https URI, for which Service.Open() would execute without errors. Another thread would keep accessing https://OurIp:443/OurService?wsdl to ensure that the service was available. Accessing the WSDL used to fail with:

The underlying connection was closed: An unexpected error occurred on a send.

Using ServicePointManager.SecurityProtocol with applicable settings did not work. Playing with server roles and features did not help either. Then stepped in Jaise George, the SE, resolving the issue in a couple of minutes. Jaise installed a self signed certificate in the IIS, poofing the issue. This is what he did to address the issue:

(1) Open IIS manager (inetmgr) (2) Click on the server node in the left panel, and double click "Server certificates". (3) Click on "Create Self-Signed Certificate" on the right panel and type in anything you want for the friendly name. (4) Click on “Default Web site” in the left panel, click "Bindings" on the right panel, click "Add", select "https", select the certificate you just created, and click "OK" (5) Access the https URL, it should be accessible.

DiligentKarma
  • 5,198
  • 1
  • 30
  • 33
  • 1
    You would've thought the server admin would've added the SSL cert, though! D'oh! lol :) I can see this happening, though - or an expired cert that needs renewing would likely be the more applicable scenario. – vapcguy Apr 21 '20 at 08:47
3

You just change your application version like 4.0 to 4.6 and publish those code.

Also add below code lines:

httpRequest.ProtocolVersion = HttpVersion.Version10; 
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Derk Jan Speelman
  • 11,291
  • 4
  • 29
  • 45
Viresh
  • 39
  • 2
1

Using a HTTP debugging proxy can cause this - such as Fiddler.

I was loading a PFX certificate from a local file (authentication to Apple.com) and it failed because Fiddler wasn't able to pass this certificate on.

Try disabling Fiddler to check and if that is the solution then you need to probably install the certificate on your machine or in some way that Fiddler can use it.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
0

The below code solved my problem :

request.ProtocolVersion = HttpVersion.Version10; // THIS DOES THE TRICK
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Colonel Software
  • 1,401
  • 1
  • 11
  • 16
0

It's either no shared TLS setting &/or no shared cipher between your app-domain (the client) and the site (the server). Each app-domain (e.g. powershell, cmd, your app etc.) picks up the host's default client settings (or overrides them), so you could have e.g. cmd shell working, but powershell failing etc.

You can check the target url using e.g. https://www.ssllabs.com/ssltest/analyze.html?d=api.nuget.org

and you can check your app-domains setting using e.g.

[Net.ServicePointManager]::SecurityProtocol
and
Get-TlsCipherSuite | Format-Table Name

Weakening your TLS settings to e.g. allow ssl3, might work short term, but seems like a bad idea to me.

timB33
  • 1,977
  • 16
  • 33
0

In my case, none of the above seemed to work. Until someone pointed me to the following website Check if TLS 1.2 is enabled

It seemed that the server only had records in registry for SSL 2.0 and SSL 3.0. After adding the records for TLS protocols, the problem disappeared.

Protocols

Bernard Moeskops
  • 1,203
  • 1
  • 12
  • 16