Solution pour l'erreur cURL error 7: Failed to connect to XXX port 443: Connection refused photo 1

Solution pour l’erreur cURL error 7: Failed to connect to XXX port 443: Connection refused

Sur un serveur hébergé en Chine continentale, j’ai eu la surprise de ne pas être en mesure de mettre à jour wp-cli:

wp cli update

Error: Failed to get url 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100': cURL error 7: Failed to connect to api.github.com port 443: Connection refused.Code language: JavaScript (javascript)

Visiblement, certaines adresses sont injoignables, notamment lorsqu’elles utilisent le port 443 (https).

Evidemment, on peut télécharger wp-cli manuellement et le réinstaller mais si vous souhaitez une solution plus rapide, voilà comment j’ai procédé.

Première solution: édition de /etc/hosts

1. On récupère l’adresse IP de l’adresse api.github.com:

curl --ipv4 -v https://api.github.com

Résultat: 13.250.94.254 port 443Code language: JavaScript (javascript)

2. On édite le fichier /etc/hosts du serveur:

nano /etc/hosts

3. On y ajoute l’adresse IP correspondante à api.github.com:

13.250.94.254 api.github.comCode language: CSS (css)

Et voilà, le téléchargement depuis github est de nouveau accessible.

Lire la suite

PHP: résoudre l'erreur "file_get_contents(): SSL operation failed with code 1" photo

PHP: résoudre l’erreur “file_get_contents(): SSL operation failed with code 1”

J’ai récemment utilisé l’API de YouTube pour récupérer plusieurs informations sur des vidéos, afin d’ajouter au site les données structurées adéquates.

En local, lorsque l’on utilise file_get_contents() sur une URL HTTPS, on peut parfois obtenir une erreur SSL si PHP ne trouve pas le bon bundle de certificats d’autorités de certification.

L’erreur ressemble souvent à ceci :

Warning: file_get_contents(): SSL operation failed with code 1.
OpenSSL Error messages:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed in ...php on line 2

Warning: file_get_contents(): Failed to enable crypto in ...php on line 2

Warning: file_get_contents(https://example.com/api?format=json): failed to open stream: operation failed in ...php on line 2Code language: JavaScript (javascript)

Le message peut varier selon la version de PHP, OpenSSL, le système, le conteneur Docker ou l’environnement local. Mais l’idée reste la même : PHP tente de vérifier le certificat TLS du serveur distant, et cette vérification échoue.

Depuis PHP 5.6, les flux chiffrés vérifient les certificats et les noms d’hôte par défaut. En théorie, c’est une très bonne chose. En pratique, cela expose vite une configuration locale incomplète, notamment quand le bundle CA est absent, obsolète ou mal déclaré. Documentation PHP : changements OpenSSL depuis PHP 5.6

Pourquoi cette erreur SSL apparaît

Lorsque PHP récupère une ressource HTTPS avec file_get_contents(), il utilise les streams PHP et OpenSSL. Pour accepter la connexion, PHP doit pouvoir vérifier que le certificat du site distant a été signé par une autorité de certification reconnue.

Si PHP ne trouve pas le fichier contenant les certificats racines, la vérification échoue. La requête est alors bloquée, même si l’URL fonctionne parfaitement dans votre navigateur.

Les causes les plus fréquentes sont :

  • un bundle CA absent ou trop ancien ;
  • une directive openssl.cafile non configurée ;
  • une directive curl.cainfo non configurée pour cURL ;
  • un environnement local ou Docker minimaliste ;
  • une vieille version d’OpenSSL ;
  • un proxy, antivirus ou firewall qui intercepte HTTPS ;
  • un certificat distant réellement mal configuré.

Sur un serveur Linux bien configuré, le système fournit généralement les certificats racines. Sur un environnement local, un conteneur Docker ou une installation PHP manuelle, il faut parfois les déclarer explicitement.

Méthode 1 : vérifier le fichier php.ini réellement utilisé

Avant de modifier la configuration, il faut vérifier quel fichier php.ini PHP utilise réellement.

En ligne de commande :

php --ini

Vous devriez obtenir quelque chose comme :

Loaded Configuration File: /etc/php/8.3/cli/php.iniCode language: JavaScript (javascript)

Attention : PHP en ligne de commande et PHP-FPM n’utilisent pas forcément le même fichier php.ini.

Pour un site web servi via PHP-FPM, le fichier peut plutôt être :

/etc/php/8.3/fpm/php.ini

Ou, selon la version :

/etc/php/8.4/fpm/php.ini

On peut aussi vérifier les valeurs OpenSSL directement avec PHP :

php -i | grep -E "openssl.cafile|openssl.capath|curl.cainfo"Code language: JavaScript (javascript)

Si les valeurs sont vides, PHP dépendra de la configuration par défaut du système. Cela peut suffire sur un serveur bien installé, mais échouer en local ou dans un conteneur.

Méthode 2 : installer ou mettre à jour le bundle CA du système

Sur Debian ou Ubuntu, commencez par vérifier que le paquet ca-certificates est installé :

sudo apt update
sudo apt install ca-certificates
sudo update-ca-certificates

Sur Alpine Linux, souvent utilisé dans des images Docker légères :

apk add --no-cache ca-certificates
update-ca-certificates

Sur macOS avec Homebrew, le chemin peut varier, mais le bundle OpenSSL se trouve souvent dans un répertoire Homebrew :

brew install openssl ca-certificates

Ensuite, il faut indiquer à PHP où trouver le fichier CA, si PHP ne le détecte pas automatiquement.

Méthode 3 : configurer openssl.cafile dans php.ini

La directive openssl.cafile indique à PHP le chemin du fichier contenant les certificats d’autorités de certification. La documentation PHP précise que cette option sert à définir le fichier CA utilisé avec verify_peer pour authentifier le serveur distant. Documentation PHP : configuration OpenSSL

Sur Debian ou Ubuntu, le chemin courant est souvent :

/etc/ssl/certs/ca-certificates.crt

Dans php.ini, ajoutez ou adaptez :

openssl.cafile = /etc/ssl/certs/ca-certificates.crtCode language: JavaScript (javascript)

Sur macOS avec Homebrew, un chemin possible peut être :

openssl.cafile = /opt/homebrew/etc/openssl@3/cert.pemCode language: JavaScript (javascript)

Sur une ancienne installation Intel Homebrew, le chemin peut plutôt ressembler à :

openssl.cafile = /usr/local/etc/openssl/cert.pemCode language: JavaScript (javascript)

Après modification, redémarrez PHP-FPM si le problème concerne le site web :

sudo systemctl restart php8.3-fpmCode language: CSS (css)

Pour PHP en ligne de commande, relancez simplement la commande ou le terminal.

Méthode 4 : configurer curl.cainfo pour cURL

Si vous utilisez cURL au lieu de file_get_contents(), configurez aussi curl.cainfo. La documentation PHP indique que cette directive définit la valeur par défaut de l’option CURLOPT_CAINFO, et qu’elle doit être un chemin absolu. Documentation PHP : configuration cURL

curl.cainfo = /etc/ssl/certs/ca-certificates.crtCode language: JavaScript (javascript)

Dans beaucoup d’environnements, je configure les deux :

openssl.cafile = /etc/ssl/certs/ca-certificates.crt
curl.cainfo = /etc/ssl/certs/ca-certificates.crtCode language: JavaScript (javascript)

Cela couvre à la fois les streams PHP et les requêtes cURL.

Méthode 5 : utiliser Composer CA Bundle

Si vous développez une application PHP distribuée sur plusieurs environnements, le paquet Composer composer/ca-bundle peut aider à localiser un bundle CA utilisable.

Il permet notamment de détecter le fichier CA du système ou de fournir un chemin portable depuis PHP. GitHub : composer/ca-bundle

composer require composer/ca-bundleCode language: JavaScript (javascript)

Exemple avec cURL :

<?php

use Composer\CaBundle\CaBundle;

$ca_file = CaBundle::getBundledCaBundlePath();

$ch = curl_init( 'https://www.googleapis.com/youtube/v3/videos' );

curl_setopt_array(
    $ch,
    array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CAINFO         => $ca_file,
        CURLOPT_TIMEOUT        => 15,
    )
);

$response = curl_exec( $ch );

if ( false === $response ) {
    throw new RuntimeException( curl_error( $ch ), curl_errno( $ch ) );
}

curl_close( $ch );Code language: HTML, XML (xml)

Cette approche est utile pour les librairies, les outils CLI ou les environnements locaux hétérogènes.

Méthode 6 : utiliser cURL à la place de file_get_contents()

Une autre solution consiste à remplacer file_get_contents() par une fonction basée sur cURL. Cela donne plus de contrôle : timeout, code HTTP, erreurs, user agent, redirections et certificat CA.

PHP prend en charge libcurl, qui permet de communiquer avec de nombreux protocoles, dont HTTP et HTTPS. Documentation PHP : extension cURL

Voici une version modernisée de la fonction :

<?php
declare(strict_types=1);

/**
 * Fetch a remote URL using cURL.
 *
 * This function returns the response body as a string, or false on failure.
 * SSL verification remains enabled.
 *
 * @param string      $url     Remote URL.
 * @param string|null $ca_file Optional absolute path to a CA bundle.
 * @return string|false Response body, or false on failure.
 */
function sky_curl_get_file_contents( string $url, ?string $ca_file = null ): string|false {
    if ( '' === trim( $url ) || false === filter_var( $url, FILTER_VALIDATE_URL ) ) {
        return false;
    }

    $ch = curl_init( $url );

    if ( false === $ch ) {
        return false;
    }

    $options = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_USERAGENT      => 'SkyMindsBot/1.0 (+https://www.skyminds.net/)',
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
    );

    if ( null !== $ca_file && is_readable( $ca_file ) ) {
        $options[ CURLOPT_CAINFO ] = $ca_file;
    }

    curl_setopt_array( $ch, $options );

    $body      = curl_exec( $ch );
    $http_code = (int) curl_getinfo( $ch, CURLINFO_RESPONSE_CODE );

    curl_close( $ch );

    if ( false === $body || 200 > $http_code || 300 <= $http_code ) {
        return false;
    }

    return $body;
}Code language: HTML, XML (xml)

Exemple d’utilisation :

<?php

$response = sky_curl_get_file_contents(
    'https://www.googleapis.com/youtube/v3/videos?id=VIDEO_ID&part=snippet&key=API_KEY',
    '/etc/ssl/certs/ca-certificates.crt'
);

if ( false === $response ) {
    // Prévoir une valeur de secours ou un log d’erreur.
    return;
}

$data = json_decode( $response, true );

if ( ! is_array( $data ) ) {
    // JSON invalide ou réponse inattendue.
    return;
}Code language: HTML, XML (xml)

Cette version est déjà plus saine que la fonction initiale, car elle ajoute un timeout, vérifie le code HTTP et garde la validation SSL active.

Version cURL avec erreurs détaillées

Pour déboguer, il vaut mieux retourner une structure détaillée plutôt qu’un simple false. Cela permet de savoir si l’erreur vient de TLS, du DNS, du timeout, d’un code HTTP ou du JSON.

<?php
declare(strict_types=1);

/**
 * Fetch a remote URL with cURL and return a detailed result.
 *
 * @param string      $url     Remote URL.
 * @param string|null $ca_file Optional absolute path to a CA bundle.
 * @return array{
 *     ok: bool,
 *     body: string,
 *     http_code: int,
 *     error: string,
 *     errno: int
 * }
 */
function sky_http_get_with_curl( string $url, ?string $ca_file = null ): array {
    $result = array(
        'ok'        => false,
        'body'      => '',
        'http_code' => 0,
        'error'     => '',
        'errno'     => 0,
    );

    if ( '' === trim( $url ) || false === filter_var( $url, FILTER_VALIDATE_URL ) ) {
        $result['error'] = 'Invalid URL.';
        return $result;
    }

    $ch = curl_init( $url );

    if ( false === $ch ) {
        $result['error'] = 'Unable to initialise cURL.';
        return $result;
    }

    $options = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_USERAGENT      => 'SkyMindsBot/1.0 (+https://www.skyminds.net/)',
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
    );

    if ( null !== $ca_file && is_readable( $ca_file ) ) {
        $options[ CURLOPT_CAINFO ] = $ca_file;
    }

    curl_setopt_array( $ch, $options );

    $body = curl_exec( $ch );

    $result['errno']     = curl_errno( $ch );
    $result['error']     = curl_error( $ch );
    $result['http_code'] = (int) curl_getinfo( $ch, CURLINFO_RESPONSE_CODE );

    curl_close( $ch );

    if ( false === $body ) {
        return $result;
    }

    $result['body'] = $body;
    $result['ok']   = 200 <= $result['http_code'] && 300 > $result['http_code'];

    return $result;
}Code language: HTML, XML (xml)

Exemple :

<?php

$result = sky_http_get_with_curl( 'https://example.com/api.json' );

if ( ! $result['ok'] ) {
    error_log(
        sprintf(
            'HTTP request failed. Code: %d. cURL errno: %d. Error: %s',
            $result['http_code'],
            $result['errno'],
            $result['error']
        )
    );

    return;
}

$data = json_decode( $result['body'], true );Code language: HTML, XML (xml)

C’est plus verbeux, mais nettement plus utile en production. Un false tout seul, c’est une porte fermée sans poignée.

Méthode 7 : utiliser un contexte SSL avec file_get_contents()

Si vous voulez garder file_get_contents(), vous pouvez lui passer un contexte SSL avec stream_context_create().

Les options SSL des streams PHP permettent notamment de définir cafile, verify_peer et verify_peer_name. Documentation PHP : options de contexte SSL

<?php

$context = stream_context_create(
    array(
        'ssl' => array(
            'cafile'           => '/etc/ssl/certs/ca-certificates.crt',
            'verify_peer'      => true,
            'verify_peer_name' => true,
        ),
        'http' => array(
            'timeout' => 15,
            'header'  => "User-Agent: SkyMindsBot/1.0\r\n",
        ),
    )
);

$response = file_get_contents( 'https://example.com/api.json', false, $context );

if ( false === $response ) {
    // Gérer l’erreur proprement.
    return;
}Code language: HTML, XML (xml)

Cette méthode garde la vérification SSL active, tout en indiquant explicitement le bundle CA à utiliser.

Le faux bon conseil : désactiver la vérification SSL

On trouve souvent cette “solution” sur Internet :

<?php

$context = stream_context_create(
    array(
        'ssl' => array(
            'verify_peer'      => false,
            'verify_peer_name' => false,
        ),
    )
);

$response = file_get_contents( 'https://example.com/api.json', false, $context );Code language: HTML, XML (xml)

Techniquement, cela peut faire disparaître l’erreur. Mais ce n’est pas une vraie correction. Cela revient à dire à PHP : “ne vérifie pas que le serveur distant est bien celui qu’il prétend être”.

À réserver uniquement à un test local très ponctuel, jamais à du code de production. La bonne solution consiste à corriger le bundle CA, pas à fermer les yeux.

Cas WordPress : utiliser wp_remote_get()

Dans WordPress, il vaut mieux éviter file_get_contents() pour appeler une API distante. Utilisez plutôt l’API HTTP de WordPress avec wp_remote_get().

La fonction wp_remote_get() effectue une requête HTTP GET et retourne soit un tableau de réponse, soit un WP_Error. La documentation WordPress précise aussi que si l’URL est contrôlée par l’utilisateur, il faut utiliser wp_safe_remote_get(). Documentation WordPress : wp_remote_get()

<?php
/**
 * Fetch JSON from a remote API using the WordPress HTTP API.
 *
 * @param string $url Remote API URL.
 * @return array<string,mixed>|null Decoded JSON, or null on failure.
 */
function sky_fetch_remote_json( string $url ): ?array {
    $response = wp_remote_get(
        $url,
        array(
            'timeout'     => 15,
            'redirection' => 3,
            'headers'     => array(
                'Accept' => 'application/json',
            ),
        )
    );

    if ( is_wp_error( $response ) ) {
        error_log( 'Remote request failed: ' . $response->get_error_message() );
        return null;
    }

    $status_code = wp_remote_retrieve_response_code( $response );

    if ( 200 > $status_code || 300 <= $status_code ) {
        error_log( 'Remote request returned HTTP ' . $status_code );
        return null;
    }

    $body = wp_remote_retrieve_body( $response );

    if ( '' === $body ) {
        return null;
    }

    $data = json_decode( $body, true );

    return is_array( $data ) ? $data : null;
}Code language: HTML, XML (xml)

Dans un plugin ou un thème WordPress, c’est plus propre : WordPress gère la couche HTTP, les transports disponibles, les erreurs, les timeouts, et l’intégration avec l’environnement.

Cas WordPress : wp_safe_remote_get()

Si l’URL peut venir d’un utilisateur, d’un champ d’option, d’un import ou d’un webhook, utilisez wp_safe_remote_get(). Cette fonction applique des contrôles supplémentaires pour limiter certains abus, notamment autour des URLs potentiellement dangereuses.

<?php

$response = wp_safe_remote_get(
    esc_url_raw( $url ),
    array(
        'timeout'     => 15,
        'redirection' => 3,
    )
);Code language: HTML, XML (xml)

Pour une URL d’API codée en dur et maîtrisée, wp_remote_get() suffit. Pour une URL variable, wp_safe_remote_get() est préférable.

Tester le certificat distant avec openssl

Avant d’accuser PHP, vérifiez aussi le certificat du serveur distant.

openssl s_client -connect example.com:443 -servername example.com -showcertsCode language: CSS (css)

Pour un diagnostic plus direct avec cURL en ligne de commande :

curl -Iv https://example.com/Code language: JavaScript (javascript)

Si curl échoue aussi côté système, le problème ne vient probablement pas de PHP seul. Il peut venir du bundle CA système, du certificat distant, ou d’un proxy réseau.

Cas Docker et environnements locaux

Dans Docker, l’erreur apparaît souvent parce que l’image est minimale et ne contient pas les certificats racines.

Pour une image Debian ou Ubuntu :

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && update-ca-certificates \
    && rm -rf /var/lib/apt/lists/*Code language: JavaScript (javascript)

Pour Alpine :

RUN apk add --no-cache ca-certificates \
    && update-ca-certificates

Ensuite, vérifiez que PHP connaît le bon chemin :

php -i | grep -E "openssl.cafile|curl.cainfo"Code language: JavaScript (javascript)

Dans Local, MAMP, XAMPP ou un environnement PHP installé à la main, le principe reste le même : trouver le php.ini réellement utilisé, puis déclarer un bundle CA lisible.

Mémo rapide

# Vérifier le php.ini utilisé en CLI.
php --ini

# Vérifier les valeurs OpenSSL/cURL.
php -i | grep -E "openssl.cafile|openssl.capath|curl.cainfo"

# Installer le bundle CA sur Debian/Ubuntu.
sudo apt update
sudo apt install ca-certificates
sudo update-ca-certificates

# Déclarer le bundle CA dans php.ini.
openssl.cafile = /etc/ssl/certs/ca-certificates.crt
curl.cainfo = /etc/ssl/certs/ca-certificates.crt

# Redémarrer PHP-FPM.
sudo systemctl restart php8.3-fpm

# Tester une URL HTTPS côté système.
curl -Iv https://example.com/

# Tester le certificat distant.
openssl s_client -connect example.com:443 -servername example.com -showcertsCode language: PHP (php)

Conclusion

L’erreur file_get_contents(): SSL operation failed with code 1 indique souvent que PHP ne parvient pas à vérifier le certificat TLS du serveur distant. Dans la majorité des cas, le problème vient d’un bundle CA absent, obsolète ou mal déclaré.

La première correction consiste donc à installer les certificats racines du système, puis à configurer openssl.cafile et, si besoin, curl.cainfo dans le bon php.ini.

Si vous développez dans WordPress, utilisez plutôt wp_remote_get() ou wp_safe_remote_get(). Si vous écrivez une application PHP classique, cURL offre plus de contrôle que file_get_contents(), notamment pour les timeouts, les erreurs et la configuration SSL.

Enfin, évitez de désactiver la vérification SSL pour “corriger” le problème. Cela masque l’erreur, mais cela retire précisément la protection que TLS est censé apporter. Mieux vaut réparer le certificat que casser le thermomètre.