The SOCKS connection and TLS connection have different jobs. First obtain a byte stream to the destination through SOCKS. Then start TLS over that stream, identifying the destination website rather than the proxy.

For an HTTPS-style TLS endpoint, the arrangement can be:

use IO::Socket::Socks;
use IO::Socket::SSL;

my $target = 'example.com';
my $socket = IO::Socket::Socks->new(
    SocksVersion => 5,
    ProxyAddr => '127.0.0.1',
    ProxyPort => 1080,
    ConnectAddr => $target,
    ConnectPort => 443,
) or die $!;

IO::Socket::SSL->start_SSL(
    $socket,
    SSL_hostname => $target,
    SSL_verifycn_name => $target,
    SSL_verifycn_scheme => 'http',
    SSL_verify_mode => SSL_VERIFY_PEER,
) or die IO::Socket::SSL::errstr();

For a public HTTPS target on a permitted public-proxy route, the SOCKS5 proxy list offers candidates for ProxyAddr and ProxyPort. The fragment establishes the transport layers; the application must still send the protocol expected by the destination and close the socket when finished.

Keep the intended peer name

The proxy endpoint belongs to the first connection. TLS SNI and hostname verification concern example.com. Using the proxy hostname at that stage can request or verify the wrong identity.

Trust-store configuration is another independent requirement. Use the application's intended CA configuration rather than a trust-everything callback copied from a diagnostic example.

If the SOCKS step fails, changing TLS parameters will not fix it. If SOCKS succeeds and TLS fails, retain the certificate and hostname error as evidence instead of treating it as a generic proxy outage.

The module names also matter: IO::Socket::Socks and IO::Socket::SSL expose different constructors and upgrade behavior. A raw TCP socket that merely reached the proxy has not completed the SOCKS destination negotiation yet.

Allakkat attuumassuteqartut