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:
- Enabled verbose cURL output with
CURLOPT_VERBOSE => true
- Used
openssl s_client -connect localhost:443 -debug
for handshake analysis - 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