C 30 сентября 2021 года на сайтах WordPress, для которых был установлен сертификат Let’s Encrypt, могут перестать работать HTTP API запросы.
Например такой запрос будет выдавать ошибку:
$res = wp_remote_get( 'https://wp-kama.ru/' ); if( is_wp_error( $res ) ){ echo $res->get_error_message(); }
Получим:
cURL error 60: SSL certificate problem: certificate has expired
Такую ошибку можно будет увидеть где угодно, например:
- в админке при проверке обновлений WordPress
- при проверке обновлений плагинов
- при обращении к апи любого сервиса. Например, перестанет работать плагин TinyPNG — JPEG, PNG & WebP image compression и куча других, которые используют какие бы то ни было запросы.
Почему мы видим ошибку certificate has expired?
Почему так происходит подробно расписано на хабре.
Если коротко, то в ядре WP есть файл корневых сертификатов /wp-includes/certificates/ca-bundle.crt
который используется для проверки SSL всех запросов созданных через HTTP API. В этом файле просрочен один из корневых сертификатов на основе которого был создан сертификат для вашего сайта. Поэтому запрос не может пройти проверку и выдается такая ошибка.
С очередными обновлениями WP эта ошибка должна пропасть, но вот что делать если решение нужно уже сегодня, или если вы не планируете обновлять WordPress, а рабочие HTTP запросы нужны.
Решение ошибки: cURL error 60: SSL certificate has expired
Вариант 1
Нужно обновить контент файла /wp-includes/certificates/ca-bundle.crt
изменить его на контент этого файла https://curl.haxx.se/ca/cacert.pem.
Изменять в данном случае файл ядра допустимо, потому что при следующем обновлении WP проблема исчезнет. См. соответствующий коммит на GitHub.
Это можно сделать вручную:
- Скачайте файл по ссылке https://curl.haxx.se/ca/cacert.pem.
- Обновите контент
/wp-includes/certificates/ca-bundle.crt
контентом из скаченного файла.
Или используйте следующий код
Использовать код удобно, когда у вас есть возможность запустить код из админки или как-то еще, например через плагин Code Snippets.
Добавьте следующий код куда угодно и перейдите на страницу http://ВАШСАЙТ.com/?update-wp-ca-bundle
.
/** * Goto http://yoursite.com/?update-wp-ca-bundle */ if( isset( $_GET['update-wp-ca-bundle'] ) ){ $crt_file = ABSPATH . WPINC . '/certificates/ca-bundle.crt'; $new_crt_url = 'https://curl.haxx.se/ca/cacert.pem'; if( is_writable( $crt_file ) ){ $new_str = file_get_contents( $new_crt_url ); if( $new_str && strpos( $new_str, 'Bundle of CA Root Certificates' ) ){ $up = file_put_contents( $crt_file, $new_str ); echo $up ? 'OK: ca-bundle.crt updated' : 'ERROR: can`t put data to ca-bundle.crt'; } else { echo 'ERROR: cant download https://curl.haxx.se/ca/cacert.pem'; } } else { echo 'ERROR: ca-bundle.crt not writable'; } exit; }
После использования, код нужно удалить.
Вариант 2
Решить проблему можно через хук http_request_args. Этот хук нужно использовать в MU плагине.
-
Создайте файл
loader.php
в папкеwp-content/mu-plugins
(если такой папки у вас нет, создайте её). -
Добавьте следующий код в этот файл:
<?php require_once __DIR__ .'/fix-wp-ca-bundle/main.php';
-
Создайте папку
wp-content/mu-plugins/fix-wp-ca-bundle
. -
Создайте файлы:
main.php
иca-bundle.crt
в папкеfix-wp-ca-bundle
.Добавьте следующий код в эти файлы.
Код файла
main.php
:<?php defined( 'ABSPATH' ) || exit; /** * Update the path to the WordPress trusted root certificates. * * Actual certificates can be downloaded at this link: http://curl.haxx.se/ca/cacert.pem */ add_filter( 'http_request_args', 'http_request_change_sslsertificates' ); function http_request_change_sslsertificates( $parsed_args ){ $parsed_args[ 'sslcertificates' ] = __DIR__ . '/ca-bundle.crt'; return $parsed_args; }
Контент файла
ca-bundle.crt
:Скопируйте контент этого файла http://curl.haxx.se/ca/cacert.pem
Должна получится такая структура:
Готово! Теперь все должно работать как и прежде.
The LetsEncrypt expiration is a bit confusing because the expired certificate is left in the chain for backwards compatibility purposes from what we’ve read on their blog.
So there are two paths for the certificates, the new shorter valid path, and the longer expired path.
That is to say the issue is likely not that the trust store is missing a certificate, but that postman is ignoring the valid certificate path.
Update:
If postman is using a version of NodeJS older than 5 years it’s likely they don’t have the ISRG Root X1 in the trust chain.
https://github.com/nodejs/node/blame/master/src/node_root_certs.h#L2602-L2631
If someone wants to try you can extend the built in certificates via the environment variable NODE_EXTRA_CA_CERTS
nodejs/node#9139
Update 2:
I’ve tried manually adding the CA for ISRG Root X1 from NodeJS to no avail. It’s likely a conflict in the TLS resolution paths. If you want to try yourself just copy and paste the certificate I linked here into a file ending with .pem
and import it into postman as you are accustomed to.
I don’t know much about Postman so maybe someone can figure out how to get more details from there or correctly configure the certificate.
We running 2 application on amazon EC2 (backend.example.com
& frontend.example.com
). For that application, we used a paid SSL Certificate. That certificate expiration date at 2021 June. But today, we got an error —
cURL error 60: SSL certificate problem: certificate has expired (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)
We check certificate expiration date, but there was no problem (2021 June). Then we follow this thread — curl: (60) SSL certificate problem: unable to get local issuer certificate (@Dahomz answer)
After that, when we curl example.com
by — curl -v --url https://backend.example.com --cacert /etc/ssl/ssl.cert/cacert.pem
, It working fine. Response like —
* Rebuilt URL to: https://backend.example.com/
* Trying 127.0.0.1...
* Connected to backend.example.com (127.0.0.1) port 443 (#0)
* found 139 certificates in /etc/ssl/ssl.cert/cacert.pem
* found 600 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ******_RSA_***_***_GCM_*****
* server certificate verification OK
* server certificate status verification SKIPPED
* common name: *.example.com (matched)
* server certificate expiration date OK
* server certificate activation date OK
* certificate public key: RSA
* certificate version: #3
* subject: OU=Domain Control Validated,OU=PositiveSSL Wildcard,CN=*.example.xyz
* start date: Mon, 04 May 2019 00:00:00 GMT
* expire date: Wed, 07 June 2021 23:59:59 GMT
* issuer: C=GB,ST=Greater Manchester,L=Salford,O=Sectigo Limited,CN=Sectigo RSA Domain Validation Secure Server CA
* compression: NULL
* ALPN, server accepted to use http/1.1
But when we hit from frontend.example.com
to backend.example.com
by curl, it throws this error —
* Rebuilt URL to: https://backend.example.com/
* Trying 127.0.0.1...
* Connected to backend.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/ssl.cert/cacert.pem
CApath: /etc/ssl/certs
* SSL connection using TLSv1.2 / *****-RSA-*****-GCM-******
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.example.com
* start date: Mar 4 00:00:00 2019 GMT
* expire date: Apr 7 23:59:59 2021 GMT
* issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA
* SSL certificate verify result: certificate has expired (10), continuing anyway.
My curl code —
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://backend.example.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_STDERR, fopen(public_path("c.log"), 'w'));
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$output = curl_exec($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
In this article
- 1. How to check the error?
- 2. How to confirm the error?
- 3. What’s the cause of the error?
- 4. How to solve the problem?
- Solution examples
- Tips based on user feedbacks
A lot of websites are using a global certificate, which expired on September 30 2021. This creates an issue when you are trying to connect to our API.
it can only be solved by your host!
1. How to check the error?
Go to our Help center and under Possible Conflicts press the Test connection button, your page will get refreshed with an error log inside the Debug information part. That log will contain a similar error:
* SSL certificate problem: certificate has expired
or:
* SSL certificate problem: unable to get local issuer certificate
2. How to confirm the error?
Create a new php
file and put the following content inside:
<?php // create curl resource $ch = curl_init(); // set url curl_setopt($ch, CURLOPT_URL, "https://api.nextendweb.com/"); //return the transfer as a string curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $errorFile = dirname(__FILE__) . '/curl_error.txt'; $out = fopen($errorFile, "w"); curl_setopt($ch, CURLOPT_VERBOSE, true); curl_setopt($ch, CURLOPT_STDERR, $out); // $output contains the output string $output = curl_exec($ch); curl_close($ch); fclose($out); echo "<h1>LOG</h1>"; echo "<pre>"; echo htmlspecialchars(file_get_contents($errorFile)); unlink($errorFile); echo "</pre>";
Upload the php
file you created into your server, e.g. via FTP and open it in your browser. For example, if you created test.php
then you can open, e.g. https://yoursite.com/test.php
. Look for lines that start with SSL
to identify the issue with your certificate.
If you need to contact your host to get this problem sorted, you can send them the link to this file which should help them understand the problem and provide an accurate fix in a timely manner.
If you see the certificate is fine here, then your WordPress certificate is the one that needs updated.
Possible example results
Below you can see some example results the code above produces that indicate problems.
2.1. Issue: Certificate not set
* Trying 172.104.28.39... * TCP_NODELAY set * Connected to api.nextendweb.com (172.104.28.39) port 443 (#0) * ALPN, offering http/1.1 * SSL certificate problem: unable to get local issuer certificate * stopped the pause stream! * Closing connection 0
Here the key error is in the 5th line, that starts with: SSL certificate problem
. In this example the cause of the problem is that PHP is unable to get local issuer certificate, which is caused by a misconfiguration of the SSL certificate on your server.
2.2. Issue: Expired certificate
* Trying 172.104.28.39... * Connected to api.nextendweb.com (172.104.28.39) port 443 (#0) * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: /etc/ssl/certs * SSL certificate problem: certificate has expired * Closing connection 0
Here the key error is in the 7th line, that starts with: SSL certificate problem
. In this example the cause of the problem is that the certificate has expired and it needs to be updated.
2.3. Server’s SSL certificate is fine
If your SSL is fine, you should see a similar log:
*Trying 172.104.28.39... * TCP_NODELAY set * Connected to api.nextendweb.com (172.104.28.39) port 443 (#0) * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: C:wamp64binapacheapache2.4.51confcacert.pem CApath: none * SSL connection using unknown / TLS_AES_256_GCM_SHA384 * ALPN, server accepted to use http/1.1 * Server certificate: * subject: CN=api1.nextendweb.com * start date: Sep 27 20:46:16 2022 GMT * expire date: Dec 26 20:46:15 2022 GMT * subjectAltName: host "api.nextendweb.com" matched cert's "api.nextendweb.com" * issuer: C=US; O=Let's Encrypt; CN=R3 * SSL certificate verify ok. > GET / HTTP/1.1 Host: api.nextendweb.com Accept: */*
The key here is the last line that starts with a star ( *
). It reads SSL certificate verify ok.
which means that your server’s certificate is fine.
If you see the certificate is fine here, but you still keep getting the message about the certificate being expired then your WordPress certificate is the one that needs updated.
3. What’s the cause of the error?
The cause of the problem is that the root SSL certificate of the server is expired on September 30, 2021. This certificate is used to communicate between two websites, and this communication can only happen via https. The communication between two servers uses a different certificate than the communication between the browser and the website.
So even if you have a valid certificate for your site for the SSL connection between the site and browser you can still have another, expired certificate on the server that’s used when your site communicates with other websites. If you receive the 60SSL certificate problem: certificate has expired error
that means the server’s root certificate has expired, and the host needs to update that.
Send your host this Let’s Encrypt article that explains the problem in detail: https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/ and tell them it’s impacting you as well.
4. How to solve the problem?
Warning: Server related problems need to be solved by the host. We’re unable to provide support for server management.
The only solution to this problem is to get your host to update the root certificate on your server.
So, you need to contact your server host and ask them to insert a new cacert.pem file into their servers, and configure it within their php.ini
file. That way your website won’t use the globally accessible certificate anymore, but it will have its own.
A website has multiple SSL certificates. If your host says, that your SSL certificate is fine, then they checked the wrong certificate! Ask them to follow the solution examples below, to fix the root certificate.
The php test file above shows the location of the root certificate your host needs to update. Creating the testing file and providing it to your host can help them understand which certificate they need to look at.
Solution examples
You can find a few examples here on how to solve the problem on certain servers. You can send these instructions to your host which they should be able to understand and apply to your own server. Depending on the used server, some steps might be different.
Warning: Server related problems need to be solved by the host. We’re unable to provide support for server management.
Solution on WampServer
- 1
-
Download this cacert.pem file.
- 2
-
Place this file into your PHP folder. Like if you are using
php7.4.9
, put the file here:
C:wamp64binphpphp7.4.9
- 3
-
Open the
php.ini
file of the server (left click on the Wamp icon → PHP → php.ini): - 4
-
Find this line:
;curl.cainfo
- 5
-
Change it to where your cacert.pem file is:
curl.cainfo = "C:wamp64binphpphp7.4.9cacert.pem"
- 6
-
Make sure you remove the ; sign at the beginning of the line!
- 7
-
Save the php.ini file.
- 8
-
Restart Wamp, and the problem should be fixed!
Solution on MAMP — Mac localhost server
- 1
-
Download this
cacert.pem file. - 2
-
Replace your MAMP server’s file with it:
/Applications/MAMP/Library/OpenSSL/certs/cacert.pem
- 3
- Restart MAMP, and the problem should be fixed!
Solution on Windows server
- 1
-
Download this cacert.pem file.
- 2
-
Place this file into your PHP folder. Like if you are using
php7.0
and your server installation happened in theProgram Files (x86)
folder, put the file here:
C:Program Files (x86)PHPv7.0
- 3
-
Open the
php.ini
file of the server. - 4
-
Find this line:
;curl.cainfo
- 5
-
Change it to where your cacert.pem file is:
curl.cainfo = "C:Program Files (x86)PHPv7.0cacert.pem"
- 6
-
Make sure you remove the ; sign at the beginning of the line!
- 7
-
Save the php.ini file.
- 8
-
Restart your server:
iisreset /restart
and the problem should be fixed!
Tips based on user feedbacks
openssl.cafile
One person with CentOS server, in his php.ini file also had to change the openssl.cafile value, to point to the new cacert.pem file:
openssl.cafile = "C:wamp64binphpphp7.4.9cacert.pem"
Expired WordPress certificate
Note: The certificate update was released in WordPress 5.9. So if you have WordPress 5.9 or higher version, then you already have the updated certificate file.
This issue is connected to a certain OpenSSL version and a WordPress certificate problem. You can learn more about it here. To solve this problem, update your wp-includes/certificates/ca-bundle.crt
file with the content you can find here: https://github.com/WordPress/WordPress/blob/master/wp-includes/certificates/ca-bundle.crt
Начиная с 1 октября 2021 начал получать много сообщений от пользователей, которые получали ошибку «SSL certificate problem: certificate has expired» при попытке обновить компонент через админку.
Ниже попытаюсь вкратце объяснить, из-за чего возникла проблема и как ее решить.
Причина ошибки:
30 сентября 2021 14:01:15 GMT закончился срок действия корневого сертификата IdenTrust DST Root CA X3.
Из-за этого стала невозможна проверка сертификатов, выпущенным центром сертификации Let’s Encrypt.
Эта проверка осуществляется при отправке запросов с вашего сервера на другой сервер через домен, который имеет SSL-сертификат Let’s Encrypt.
Потому все такие запросы, отправленные через cURL будут выдавать ошибку «SSL certificate problem: certificate has expired«
Исправление:
Если вы получаете эту ошибку при попытке обновить компонент через админку Joomla, то Вам необходимо удалить просроченный сертификат из цепочки сертификатов, которые использует Joomla.
Для этого через ФТП найдите файл /libraries/src/Http/Transport/cacert.pem (в более ранних версиях Joomla — /libraries/joomla/http/transport/cacert.pem) и скачайте его на свой ПК, чтобы сделать резервную копию.
Откройте файл и найдите строку, содержащую «DST Root CA X3«. удалите ее и все последующий блок (это и есть просроченный сертификат):
Сохраните файл на сервер.
Если же в Вашем файле сертификаты не подписаны, то найдите нужный по последним 5 буквам — CNTUQ.
Если же Вы столкнулись с ошибкой «SSL certificate problem: certificate has expired» в другом месте, то напишите в тех. поддержку своего хостинга обращение следующего содержания:
«При попытке отправки запросов через cURL получаю ошибку SSL certificate problem: certificate has expired. Удалите, пожалуйста, просроченный сертификат DST Root CA X3 из цепочки сертификатов, которую использует OpenSSL»