2026年11月12日

2026年11月12日

Fix WordPress SSL Certificate Errors: ERR_CERT Codes and cURL Error 60

This article is a localized edition of the Japanese original. Read the Japanese original

When a WordPress site shows an SSL certificate error, first decide which side is failing: is it "a visitor's browser connecting to your site" or "WordPress (PHP) connecting to an external service"? For the first, the browser's error code points you toward the cause; for the second, messages such as cURL error 60 do.

The commands in this article assume an Ubuntu / Debian server running Apache or Nginx with Let's Encrypt (certbot), as of October 2026. Replace example.com with your own domain.

Error codes and their usual causes

These are the main error codes shown by Chrome-based browsers and the first cause to suspect for each.

Error code Usual cause Section to read
NET::ERR_CERT_DATE_INVALID The certificate has expired, or the clock on the visitor's device is wrong The certificate has expired
NET::ERR_CERT_AUTHORITY_INVALID The intermediate certificate is not being sent, or a self-signed certificate is in use The intermediate certificate is missing
NET::ERR_CERT_COMMON_NAME_INVALID The domain names in the certificate do not match the domain in the URL (for example, with or without www) The domain name does not match
ERR_SSL_VERSION_OR_CIPHER_MISMATCH The server only supports old TLS (1.0/1.1), or the cipher settings do not match The TLS settings do not match
ERR_SSL_PROTOCOL_ERROR HTTPS is not actually running on port 443, or the web server is misconfigured The TLS settings do not match

If you do not get an error page and only see a warning on the padlock in the address bar, the cause is usually not the certificate but "mixed content": an HTTPS page that loads images or scripts over http://.

Check the certificate first

If you can connect to the server over SSH, use the openssl command to check the certificate the server actually returns. The same commands also work from your own computer (macOS, WSL, and so on).

# Subject, issuer and validity period of the certificate
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# Domain names included in the certificate (SAN)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

# The certificates sent (the chain) and the verification result
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>&1 \
  | grep -E "^ *[0-9] s:|^ *i:|Verify return code"

If the last command prints Verify return code: 0 (ok), the certificate chain verifies correctly. If the notAfter= date is in the past, the certificate has expired.

If certbot manages your Let's Encrypt certificates, this command also shows the expiry dates and domain names.

sudo certbot certificates

For an overall check from outside, enter your domain in Qualys SSL Server Test. It lists missing chain certificates and the TLS versions the server supports.

The certificate has expired (ERR_CERT_DATE_INVALID)

Certificates obtained with certbot are normally renewed automatically. If one has expired, automatic renewal has stopped, so renew it manually and then find out why.

# Is the renewal timer running? (its name depends on how certbot was installed)
systemctl list-timers | grep -i certbot

# Test whether renewal works (does not actually renew)
sudo certbot renew --dry-run

# Renew certificates that are close to expiry or expired
sudo certbot renew --cert-name example.com

# Make the web server load the new certificate (whichever one you use)
sudo systemctl reload apache2
sudo systemctl reload nginx

If --dry-run fails, read the error it prints. Common causes are a DNS A record pointing to another server, requests to /.well-known/acme-challenge/ being blocked by .htaccess or a WAF, or port 80 being closed.

If you use the free SSL of a rental-server company, check the certificate status and renewal settings in that company's control panel.

If the certificate has been renewed correctly but ERR_CERT_DATE_INVALID still appears on one person's device, ask them to check that the device's date and time are correct.

The intermediate certificate is missing (ERR_CERT_AUTHORITY_INVALID)

If the site opens in desktop browsers but fails on some smartphones, in curl, or from other servers, the server is often not sending the intermediate certificate. In the chain command above, if you see only 0 s: (the server certificate) and no 1 s: (the intermediate), it is missing.

With Let's Encrypt, point the server at fullchain.pem, which contains both the server certificate and the intermediate certificate.

# Check the current settings
grep -rn "SSLCertificateFile\|SSLCertificateChainFile" /etc/apache2/sites-enabled/
grep -rn "ssl_certificate" /etc/nginx/sites-enabled/
# Apache 2.4.8 or later: set SSLCertificateFile to fullchain.pem
SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Nginx
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

From Apache 2.4.8, if SSLCertificateFile points to a file that includes the intermediate certificates, SSLCertificateChainFile is not needed (SSLCertificateChainFile is deprecated). With a paid certificate, use a file in which the intermediate certificate from the issuer is appended after the server certificate.

For a detailed walkthrough, see How to check for and fix a missing intermediate SSL certificate.

Self-signed certificates (ones you created yourself) are not trusted by browsers on a public site. For a public site, use a certificate issued by a certificate authority such as Let's Encrypt.

The domain name does not match (ERR_CERT_COMMON_NAME_INVALID)

This appears, for example, when you only have a certificate for example.com but open www.example.com. Use the subjectAltName command above to check whether the domain you opened is included.

If it is not, get a new certificate that includes every domain name you need. This example uses certbot's Apache plugin (use --nginx for Nginx).

sudo certbot --apache -d example.com -d www.example.com

Also check that "WordPress Address (URL)" and "Site Address (URL)" under Settings → General use a domain name that is in the certificate, with https://.

The TLS settings do not match (ERR_SSL_VERSION_OR_CIPHER_MISMATCH / ERR_SSL_PROTOCOL_ERROR)

Current major browsers no longer connect over TLS 1.0 or 1.1. Check that the server accepts TLS 1.2 or later.

# If the connection works, certificate details are shown; if not, you get an error such as handshake failure
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null

If TLS 1.2 fails, enable TLS 1.2 and 1.3 in the web server configuration.

# Apache (mod_ssl)
SSLProtocol -all +TLSv1.2 +TLSv1.3
# Nginx
ssl_protocols TLSv1.2 TLSv1.3;

If you wrote the cipher suites (SSLCipherSuite or ssl_ciphers) yourself, old settings left behind can prevent connections. The safe approach is to generate settings for your web server and version with Mozilla's SSL Configuration Generator and replace yours with them. After changing the settings, check the syntax and then reload.

sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx

With ERR_SSL_PROTOCOL_ERROR, the openssl s_client output may say wrong version number. This means plain HTTP, not HTTPS, is answering on port 443. For Apache, check that the SSL module is enabled with a2enmod ssl and that the port 443 virtual host has SSLEngine on; for Nginx, check that it says listen 443 ssl;.

If you use Cloudflare

When traffic goes through the Cloudflare proxy, the certificate the browser sees is Cloudflare's. If the SSL/TLS encryption mode is "Full (strict)", Cloudflare also verifies your server's certificate when it connects to your server. If you get Cloudflare error 526 (Invalid SSL certificate) or 525 (SSL handshake failed), check your server's certificate and TLS settings using the steps in this article.

SSL errors when WordPress connects to external services (cURL error 60)

When WordPress connects to an external service, for plugin updates, external API integrations, payments and so on, you may see an error like this.

cURL error 60: SSL certificate problem: unable to get local issuer certificate

The certificate list WordPress uses

By default, the WordPress HTTP API, including wp_remote_get(), verifies the remote certificate not against the operating system's certificate store but against the list of root certificates bundled with WordPress (wp-includes/certificates/ca-bundle.crt). This list is refreshed when WordPress is updated. So updating the server's ca-certificates package does not change the result of the WordPress HTTP API.

The three most common causes are:

  • The remote server does not send its intermediate certificate: run openssl s_client -connect 接続先:443 -servername 接続先 -showcerts from your server, replacing the placeholder with the remote host, to check. This is a problem on the remote side, so contact its administrator.
  • WordPress is outdated: the bundled certificate list may be old and not know newer root certificates. Update WordPress to the latest version.
  • A corporate proxy or similar sits in the middle of the connection: in environments that inspect traffic with certificates from their own certificate authority, you need to specify a certificate list that includes that authority (see below).

Log the error details

To see which connection fails and with what error, write the errors to the log with the http_api_debug action. If WP_DEBUG and WP_DEBUG_LOG are set to true in wp-config.php, they are recorded in wp-content/debug.log.

<?php
/**
 * Plugin Name: Log HTTP API errors (delete after investigating)
 */
add_action( 'http_api_debug', function ( $response, $context, $class, $parsed_args, $url ) {
	if ( is_wp_error( $response ) ) {
		error_log( sprintf( 'HTTP API error: %s (%s)', $response->get_error_message(), $url ) );
	}
}, 10, 5 );

Put this code in wp-content/mu-plugins/ under a name such as log-http-errors.php (create the folder if it does not exist). Delete it when you have finished investigating.

If WP-CLI is available, you can also make one request on the spot and check the result (WP-CLI runs on the command-line PHP, which may be a different version from the PHP the web server uses).

wp eval '$r = wp_remote_get( "https://api.example.com/" ); echo is_wp_error( $r ) ? $r->get_error_message() : wp_remote_retrieve_response_code( $r ), PHP_EOL;'

You can check PHP's cURL and OpenSSL versions under Tools → Site Health, on the "Info" tab, in the cURL version field of the "Server" section. If OpenSSL is very old, it may fail to connect to services that use newer ciphers. In that case, the server's OS or PHP needs to be updated.

Remote services that use their own certificate authority

For a remote service, such as an internal system, whose certificate is issued by its own certificate authority, you can tell WordPress to use the operating system's certificate list for that service only. Change sslcertificates with the http_request_args filter. The path is an Ubuntu / Debian example and assumes the authority's certificate has been added to the OS.

<?php
add_filter( 'http_request_args', function ( $args, $url ) {
	// Verify with the OS certificate list for the target service only
	if ( str_starts_with( $url, 'https://internal.example.jp/' ) ) {
		$args['sslcertificates'] = '/etc/ssl/certs/ca-certificates.crt';
	}
	return $args;
}, 10, 2 );

Disable certificate verification only in development

Setting sslverify to false, or returning false from the https_ssl_verify filter, stops WordPress from verifying the remote certificate, so you would not notice if the traffic were intercepted or altered. Do not use it on a public site; limit it to local development environments that use self-signed certificates.

<?php
// Use only in a local development environment. Do not put this on a public site
if ( 'local' === wp_get_environment_type() ) {
	add_filter( 'https_ssl_verify', '__return_false' );
}

There is also an https_local_ssl_verify filter, but it is only used when WP-Cron or Site Health connects to your own site (loopback requests), and its default is already false. It does not affect connections to external services.

Even in development, you can avoid disabling verification by creating certificates with a tool that sets up a local certificate authority (such as mkcert) and trusting that authority.

Summary

  • For browser errors, use the error code to narrow down the cause and check the certificate the server returns with openssl s_client
  • Renew an expired certificate with certbot renew, then find out why automatic renewal stopped (DNS, access to acme-challenge, ports)
  • Fix a missing intermediate certificate by pointing to fullchain.pem. Fix a name mismatch by getting a new certificate that includes every domain name you need
  • Make sure TLS 1.2 or later works, and replace cipher suites with the settings from Mozilla's generator
  • For cURL error 60, remember that WordPress verifies against its bundled certificate list, and check the remote chain and your WordPress version. Disable verification only in development

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲