← Knowledge base

PHP

Is PQC enabled? — quick check

macOS / Linux / Windows

# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true

# 2) Dependency check — prompt before installing anything.
if ! command -v php >/dev/null 2>&1; then
  echo 'PHP CLI was not found.'
  printf 'Install or enable PHP CLI now? [y/N] '
  read answer
  case "$answer" in
    [Yy]*) echo 'Install PHP from your OS package manager, then rerun this snippet.' ;;
    *) echo 'Skipping PHP CLI-based check.'; exit 1 ;;
  esac
fi

# 3) Capability — offline.
php -r "echo OPENSSL_VERSION_TEXT, PHP_EOL;"

# 4) Liveness — confirm PHP's openssl extension can complete TLS 1.3.
#    PHP exposes negotiated cipher/protocol via stream_get_meta_data()
#    but NOT the named group, so the authoritative answer comes from
#    local openssl below.
php -r '
$f = stream_socket_client("ssl://example.com:443", $e, $m, 5, STREAM_CLIENT_CONNECT);
if (!$f) { echo "handshake failed: $m
"; exit; }
$crypto = stream_get_meta_data($f)["crypto"] ?? [];
echo "proto=", $crypto["protocol"] ?? "?", " cipher=", $crypto["cipher_name"] ?? "?", "
";'

# 5) Authoritative named-group readout — still local, via openssl CLI.
# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true

# 2) Dependency check — prompt before installing anything.
if ! command -v openssl >/dev/null 2>&1; then
  echo 'OpenSSL was not found. A local PQC proof needs OpenSSL 3.5+.'
  printf 'Install OpenSSL now? [y/N] '
  read answer
  case "$answer" in
    [Yy]*)
      if command -v brew >/dev/null 2>&1; then brew install openssl@3
      elif command -v apt-get >/dev/null 2>&1; then sudo apt-get update && sudo apt-get install -y openssl
      elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y openssl
      elif command -v yum >/dev/null 2>&1; then sudo yum install -y openssl
      else echo 'No supported package manager found. Install OpenSSL 3.5+ and retry.'; exit 1
      fi ;;
    *) echo 'Install OpenSSL 3.5+ and retry for a local PQC proof.'; exit 1 ;;
  esac
fi

OPENSSL=openssl
if command -v brew >/dev/null 2>&1; then
  BREW_OPENSSL="$(brew --prefix openssl@3 2>/dev/null)/bin/openssl"
  [ -x "$BREW_OPENSSL" ] && OPENSSL="$BREW_OPENSSL"
fi

$OPENSSL version
if ! $OPENSSL list -tls-groups 2>/dev/null | grep -qiE 'X25519MLKEM768|MLKEM|Kyber'; then
  echo 'This OpenSSL does not advertise ML-KEM groups. Upgrade to OpenSSL 3.5+ or load oqsprovider, then retry.'
  exit 1
fi
$OPENSSL s_client -connect example.com:443 -tls1_3 -groups X25519MLKEM768   </dev/null 2>&1 | grep -E 'Negotiated TLS1\.3 group|Server Temp Key'

Expected when PQC is ON

OpenSSL 3.5.0 8 Apr 2025
proto=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
Negotiated TLS1.3 group: X25519MLKEM768

What you'll see when PQC is OFF

OpenSSL 3.0.13 30 Jan 2024
proto=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384
# (no group line)

To make PHP actively send the PQ group, set Groups in /etc/ssl/openssl.cnf (system-wide) — PHP has no per-stream group setter.

PHP's HTTPS streams (fopen('https://...'), file_get_contents, Guzzle/cURL, SoapClient) all ride on the system OpenSSL. Upgrade OpenSSL to 3.5+ and PHP picks up X25519MLKEM768 with no code changes.

Per-request stream context

$ctx = stream_context_create([
  'ssl' => [
    'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
    'curves'        => 'X25519MLKEM768:X25519:secp256r1',
    'verify_peer'   => true,
    'verify_peer_name' => true,
  ],
]);
echo file_get_contents('https://example.com/', false, $ctx);

The curves stream option requires PHP 8.4+ linked against OpenSSL 3.5+ (or 3.x + oqsprovider). Earlier PHP ignores it.

libcurl-php

Use CURLOPT_SSL_EC_CURVES:

$ch = curl_init('https://example.com/');
curl_setopt($ch, CURLOPT_SSL_EC_CURVES, 'X25519MLKEM768:X25519:secp256r1');

Hostinger / shared hosts

Most managed PHP hosts pin OpenSSL globally — you can't influence groups from PHP code until the host upgrades. Hostinger's LiteSpeed handles client-facing TLS itself, so the site's TLS posture is managed via hPanel, not PHP.

Run the check on your PHP service →