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

  1. Use the same passphrase string for both cert and ssl_key.
  2. Set the passphrase on only one of the two options (cert or ssl_key) and omit it from the other.
  3. 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

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


AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04). Data as JSON: /data/errors/bc7f6f35fce9bb8d.json. Report an issue: GitHub.