I think the issue is a bit different than what was analyzed so far. As far as I can see it has nothing to do with SSL_MODE_AUTO_RETRY but instead is caused by expectations on the behavior of select which are wrong with TLS 1.3.
I've added some more debugging to IO::Socket::SSL and what I saw was: - With TLS 1.2 LWP did first a write to send the request and then a read to read the response - that's expected. - With TLS 1.3 LWP first tried to read the response and then hang there, i.e. no request was even send to the server. This is obviously not what is expected. The reason for this is that LWP::protocol::http::request does not actually try to write the request first. Instead it creates a select-loop to wait for both read and write events on the socket and then acts based on what event select returns. The obvious expectation is, that there is nothing to read first but that it will be able to write the request. After the request is sent it will disable selecting for write-events and only care about reads. Only, with TLS 1.3 this expectation is wrong. Up to TLS 1.2 issuing session tickets by the server were part of the TLS handshake. With TLS 1.3 this is no longer the case and these are send after the TLS handshake is done. This means that the initial select will actually trigger a read-event since there are actual data to read on the underlying socket: the session tickets send by the server. Only, these are no application data. But sysread on a blocking socket is supposed to only return if there was at least 1 byte read or if the peer closed the connection or if a permanent error occured. And since no application data were send it just hangs. I don't see how this can be fixed in IO::Socket::SSL only since the real problem is that sysread is called at this stage (i.e. no request sent yet) in the first place. Instead the code which is making the wrong assumptions about select need to be fixed, so that sysread either not get called at this stage or at least not get called with blocking. The following small patch for LWP::protocol::http seems to fix the problem for me: --- /usr/share/perl5/LWP/Protocol/http.pm 2019-05-11 19:05:21.488561325 +0000 +++ lib/LWP/Protocol/http.pm 2019-05-11 19:40:49.332810627 +0000 @@ -374,7 +374,9 @@ if (defined($rbits) && $rbits =~ /[^\0]/) { # readable my $buf = $socket->_rbuf; + my $was_blocking = $socket->blocking(0); my $n = $socket->sysread($buf, 1024, length($buf)); + $socket->blocking(1) if $was_blocking; unless (defined $n) { die "read failed: $!" unless $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN}; # if we get here the rest of the block will do nothing Regards, Steffen