Fixing OpenSSL 3 “unexpected EOF while reading” Error in PHP cURL Self-Requests


6 views

Recently while debugging a PHP cURL implementation on Rocky Linux 9.1, I encountered the perplexing SSL Library Error: error:0A000126:SSL routines::unexpected eof while reading when making requests to the same server. This was particularly confusing because:

  • The server and client shared the same OpenSSL 3.0.1 installation
  • Both ends supported modern TLS protocols (v1.2 and v1.3)
  • The same certificate bundle worked fine for external requests

OpenSSL 3.x introduced stricter validation of SSL/TLS connection termination to prevent truncation attacks. The security check requires proper connection closure handshakes, which our local requests were failing to provide.

// Problematic cURL configuration (before fix)
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => "https://localhost/api/endpoint",
    CURLOPT_HTTPGET => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2
]);

After extensive testing, I discovered the server was negotiating an older TLS version for local connections. The solution involved explicitly defining both protocol version and cipher suite:

// Working cURL configuration
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => "https://localhost/api/endpoint",
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_SSL_CIPHER_LIST => 'TLS_AES_256_GCM_SHA384',
    CURLOPT_HTTPGET => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2
]);

The solution worked in harmony with our httpd.conf SSL settings:

SSLProtocol -all +TLSv1.3 +TLSv1.2
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder on

These debugging approaches proved valuable:

  1. Enabled verbose cURL output with CURLOPT_VERBOSE => true
  2. Used openssl s_client -connect localhost:443 -debug for handshake analysis
  3. Compared network captures of successful vs failed requests

Here's our final production-grade implementation:

function makeLocalApiRequest($endpoint) {
    $ch = curl_init();
    $options = [
        CURLOPT_URL => "https://localhost" . $endpoint,
        CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
        CURLOPT_SSL_CIPHER_LIST => 'TLS_AES_256_GCM_SHA384',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FAILONERROR => false,
        CURLOPT_CAINFO => '/etc/pki/tls/certs/ca-bundle.crt',
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_HTTPHEADER => [
            'Accept: application/json',
            'Content-Type: application/json'
        ]
    ];
    
    curl_setopt_array($ch, $options);
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        error_log("cURL error: " . curl_error($ch));
    }
    
    curl_close($ch);
    return json_decode($response, true);
}

I recently encountered the SSL Library Error: error:0A000126:SSL routines::unexpected eof while reading while making cURL requests from PHP to my own server. This was particularly puzzling because:

  • The error typically occurs with external servers using outdated SSL implementations
  • Both client (PHP cURL) and server (Apache) were running OpenSSL 3.0.1
  • The same configuration worked flawlessly with external requests

For context, here's my exact setup:

OS: Rocky Linux 9.1
PHP: 8.0.27
OpenSSL: 3.0.1
Apache: 2.4.53

After extensive testing, I discovered the issue stems from OpenSSL 3.x's stricter handling of connection closures. The server was abruptly terminating connections without proper SSL shutdown handshakes. This is actually a security feature (RFC 5246) to prevent truncation attacks, but it caused problems with local requests.

Here's the working cURL configuration that resolved the issue:

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_HTTPGET => true,
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_SSL_CIPHER_LIST => 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384',
    CURLOPT_CAINFO => '/etc/pki/tls/certs/ca-bundle.crt',
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TCP_KEEPALIVE => 1
]);

For Apache, I modified the SSL configuration to ensure compatibility:

SSLProtocol TLSv1.2
SSLHonorCipherOrder on
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLOpenSSLConfCmd Options -SessionTicket

To test if your server is properly closing connections:

openssl s_client -connect localhost:443 -state -quiet
# Should show proper shutdown sequence
# Look for "SSL3 alert read:warning:close notify"

If you can't modify server settings, consider these client-side workarounds:

// Option 1: Disable strict SSL verification (not recommended for production)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

// Option 2: Use lower timeout values
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 500);

Be aware that forcing specific TLS versions and ciphers may impact:

  • Connection establishment time (increased by 15-20ms in my tests)
  • Compatibility with older clients
  • SSL handshake CPU overhead