guzzle/guzzle · error · GuzzleHttp\Exception\InvalidArgumentException
Cannot use different passphrases for cert and ssl_key with t
Error message
Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.
What it means
Thrown by StreamHandler::setTlsPassphrase() when the cert and ssl_key options each carry a passphrase and the two passphrases differ. PHP's stream SSL context exposes a single passphrase field, so the stream handler cannot honor two different values; it fails closed rather than silently picking one.
Source
Thrown at src/Handler/StreamHandler.php:1422
throw new InvalidArgumentException(\sprintf('Invalid %s request option', $option));
}
return [$value, $passphrase];
}
private static function setTlsPassphrase(
#[\SensitiveParameter]
array &$options,
#[\SensitiveParameter]
?string $passphrase,
string $option
): void {
if ($passphrase === null) {
return;
}
if (isset($options['ssl']['passphrase']) && $options['ssl']['passphrase'] !== $passphrase) {
throw new InvalidArgumentException(\sprintf('Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.', $option));
}
$options['ssl']['passphrase'] = $passphrase;
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private static function assertStreamTlsType(string $option, $value): void
{
if (!\is_string($value) || $value === '') {
throw new InvalidArgumentException(\sprintf('%s must be a non-empty string', $option));
}
if (Psr7\Utils::asciiToUpper($value) !== 'PEM') {
throw new InvalidArgumentException(\sprintf('The stream handler only supports "PEM" for the %s request option.', $option));
}
}View on GitHub (pinned to 9b200fc580)
Solutions
- Use the same passphrase string for both cert and ssl_key.
- Set the passphrase on only one of the two options (cert or ssl_key) and omit it from the other.
- If the cert and key genuinely need different passphrases, switch to a cURL-based handler (CurlHandler/CurlMultiHandler).
Example fix
// before ['cert' => ['/c.pem', 'passA'], 'ssl_key' => ['/k.pem', 'passB']] // after ['cert' => ['/c.pem', 'shared'], 'ssl_key' => ['/k.pem', 'shared']]
Defensive patterns
Strategy: validation
Validate before calling
// Ensure cert and ssl_key share a passphrase
$certPass = is_array($options['cert'] ?? null) ? ($options['cert'][1] ?? null) : null;
$keyPass = is_array($options['ssl_key'] ?? null) ? ($options['ssl_key'][1] ?? null) : null;
if ($certPass !== null && $keyPass !== null && $certPass !== $keyPass) {
throw new InvalidArgumentException('cert and ssl_key passphrases differ');
} Prevention
- Set the passphrase on only one of cert/ssl_key; PHP's stream context uses a single passphrase field.
- If you need two distinct passphrases, use CurlHandler instead of StreamHandler.
When it happens
Trigger: Setting both ['cert' => ['/path/cert.pem', 'passA']] and ['ssl_key' => ['/path/key.pem', 'passB']] with passA !== passB in the same request options, under the StreamHandler.
Common situations: Migrating from cURL (which accepts CURLOPT_SSLCERTPASSWD and CURLOPT_SSLKEYPASSWD independently) to the stream handler, or copying two halves of a split-credential config whose passphrase fields drifted out of sync.
Related errors
- %s must be a non-empty string
- The stream handler only supports "PEM" for the %s request op
- HTTPS proxies are not supported by the stream handler.
- Invalid verify request option
- %s must be a non-empty string
AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04).
Data as JSON: /data/errors/bc7f6f35fce9bb8d.json.
Report an issue: GitHub.