Bad handshake error when renewing SSL

Please fill out the fields below so we can help you better. Note: you must provide your domain name to get help. Domain names for issued certificates are all made public in Certificate Transparency logs (e.g. crt.sh | example.com), so withholding your domain name here does not increase secrecy, but only makes it harder for us to provide help.

My domain is:

I ran this command:
sudo certbot renew

It produced this output:


Processing /etc/letsencrypt/renewal/adanisingapore.com.sg.conf


Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator webroot, Installer None
Attempting to renew cert (adanisingapore.com.sg) from /etc/letsencrypt/renewal/adanisingapore.com.sg.conf produced an unexpected error: HTTPSConnectionPool(host='acme-v02.api.letsencrypt.org', port=443): Max retries exceeded with url: /directory (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])"))). Skipping.


Processing /etc/letsencrypt/renewal/www.adanisingapore.com.sg.conf


Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator webroot, Installer None
Attempting to renew cert (www.adanisingapore.com.sg) from /etc/letsencrypt/renewal/www.adanisingapore.com.sg.conf produced an unexpected error: HTTPSConnectionPool(host='acme-v02.api.letsencrypt.org', port=443): Max retries exceeded with url: /directory (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])"))). Skipping.
All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/adanisingapore.com.sg/fullchain.pem (failure)
/etc/letsencrypt/live/www.adanisingapore.com.sg/fullchain.pem (failure)


All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/adanisingapore.com.sg/fullchain.pem (failure)
/etc/letsencrypt/live/www.adanisingapore.com.sg/fullchain.pem (failure)


2 renew failure(s), 0 parse failure(s)

My web server is (include version):
LiteSpeed/1.7.12 Open

The operating system my web server runs on is (include version):
Ubuntu 20.04.6 LTS

My hosting provider, if applicable, is:
Unsure

I can login to a root shell on my machine (yes or no, or I don't know):
Yes. I can login as root in Ubuntu

I'm using a control panel to manage my site (no, or provide the name and version of the control panel):
Unsure

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot):
certbot 0.40.0

What do you get if you run this?

curl -Lvvv https://acme-v02.api.letsencrypt.org/directory
2 Likes

I could get this:

16:42:03.704941 [0-x] == Info: [READ] client_reset, clear readers
16:42:03.859735 [0-0] == Info: Host acme-v02.api.letsencrypt.org:443 was resolved.
16:42:03.868089 [0-0] == Info: IPv6: (none)
16:42:03.871952 [0-0] == Info: IPv4: 172.65.32.248
16:42:03.875394 [0-0] == Info: [HTTPS-CONNECT] added
16:42:03.879221 [0-0] == Info: [HTTPS-CONNECT] connect, init
16:42:03.884561 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
16:42:03.889990 [0-0] == Info: Trying 172.65.32.248:443...
16:42:03.894632 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
16:42:03.901489 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
16:42:03.908858 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
16:42:03.914629 [0-0] == Info: [SSL] cf_connect()
16:42:03.921489 [0-0] == Info: [SSL] No cached session ID for https://acme-v02.api.letsencrypt.org:443
16:42:03.941906 [0-0] == Info: schannel: disabled automatic use of client certificate
16:42:03.961054 [0-0] == Info: ALPN: curl offers http/1.1
16:42:03.973986 [0-0] == Info: [SSL] cf_connect() -> 0, done=0
16:42:03.985798 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
16:42:03.999175 [0-0] == Info: [SSL] adjust_pollset, POLLIN fd=296
16:42:04.012059 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
16:42:04.024837 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
16:42:04.038187 [0-0] == Info: [SSL] cf_connect()
16:42:04.047180 [0-0] == Info: [SSL] cf_connect() -> 0, done=0
16:42:04.061849 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
16:42:04.073825 [0-0] == Info: [SSL] adjust_pollset, POLLIN fd=296
16:42:04.081246 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
16:42:04.342076 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
16:42:04.352293 [0-0] == Info: [SSL] cf_connect()
16:42:04.879508 [0-0] == Info: ALPN: server accepted http/1.1
16:42:04.885981 [0-0] == Info: [SSL] No cached session ID for https://acme-v02.api.letsencrypt.org:443
16:42:04.894523 [0-0] == Info: [SSL] Added Session ID to cache for https://acme-v02.api.letsencrypt.org:443 [server]
16:42:04.905886 [0-0] == Info: [SSL] cf_connect() -> 0, done=1
16:42:04.911042 [0-0] == Info: [HTTPS-CONNECT] connect+handshake h21: 1026ms, 1st data: 471ms
16:42:04.921268 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=1
16:42:04.930028 [0-0] == Info: Connected to acme-v02.api.letsencrypt.org (172.65.32.248) port 443
16:42:04.940823 [0-0] == Info: using HTTP/1.x
16:42:04.945706 [0-0] => Send header, 101 bytes (0x65)
0000: GET /directory HTTP/1.1
0019: Host: acme-v02.api.letsencrypt.org
003d: User-Agent: curl/8.10.1
0056: Accept: /
0063:
16:42:04.963262 [0-0] == Info: [SSL] cf_recv(len=102400) -> -1, 81
16:42:04.970138 [0-0] == Info: Request completely sent off
16:42:05.092884 [0-0] == Info: schannel: remote party requests renegotiation
16:42:05.111062 [0-0] == Info: schannel: renegotiating SSL/TLS connection
16:42:05.127107 [0-0] == Info: [SSL] Found cached session ID for https://acme-v02.api.letsencrypt.org:443
16:42:05.154622 [0-0] == Info: schannel: SSL/TLS connection renegotiated
16:42:05.171221 [0-0] == Info: [SSL] cf_recv(len=102400) -> -1, 81
16:42:05.186189 [0-0] == Info: [WRITE] cw-out is notpaused
16:42:05.198258 [0-0] == Info: schannel: remote party requests renegotiation
16:42:05.209177 [0-0] == Info: schannel: renegotiating SSL/TLS connection
16:42:05.217325 [0-0] == Info: [SSL] Found cached session ID for https://acme-v02.api.letsencrypt.org:443
16:42:05.228386 [0-0] == Info: schannel: SSL/TLS connection renegotiated
16:42:05.236857 [0-0] == Info: [SSL] cf_recv(len=102400) -> 1271, 0
16:42:05.244891 [0-0] <= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
16:42:05.253336 [0-0] == Info: [WRITE] cw_out, wrote 17 header bytes -> 17
16:42:05.261666 [0-0] == Info: [WRITE] download_write header(type=c, blen=17) -> 0
16:42:05.270833 [0-0] == Info: [WRITE] client_write(type=c, len=17) -> 0
16:42:05.277694 [0-0] <= Recv header, 15 bytes (0xf)
0000: Server: nginx
16:42:05.285370 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=15) -> 0
16:42:05.294238 [0-0] == Info: [WRITE] cw_out, wrote 15 header bytes -> 15
16:42:05.301797 [0-0] == Info: [WRITE] download_write header(type=4, blen=15) -> 0
16:42:05.309157 [0-0] == Info: [WRITE] client_write(type=4, len=15) -> 0
16:42:05.316573 [0-0] <= Recv header, 37 bytes (0x25)
0000: Date: Fri, 02 May 2025 08:42:04 GMT
16:42:05.328228 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=37) -> 0
16:42:05.336857 [0-0] == Info: [WRITE] cw_out, wrote 37 header bytes -> 37
16:42:05.343981 [0-0] == Info: [WRITE] download_write header(type=4, blen=37) -> 0
16:42:05.353216 [0-0] == Info: [WRITE] client_write(type=4, len=37) -> 0
16:42:05.359958 [0-0] <= Recv header, 32 bytes (0x20)
0000: Content-Type: application/json
16:42:05.369236 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=32) -> 0
16:42:05.377203 [0-0] == Info: [WRITE] cw_out, wrote 32 header bytes -> 32
16:42:05.385657 [0-0] == Info: [WRITE] download_write header(type=4, blen=32) -> 0
16:42:05.398783 [0-0] == Info: [WRITE] client_write(type=4, len=32) -> 0
16:42:05.411749 [0-0] <= Recv header, 22 bytes (0x16)
0000: Content-Length: 1012
16:42:05.427669 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=22) -> 0
16:42:05.441832 [0-0] == Info: [WRITE] cw_out, wrote 22 header bytes -> 22
16:42:05.455790 [0-0] == Info: [WRITE] download_write header(type=4, blen=22) -> 0
16:42:05.472230 [0-0] == Info: [WRITE] client_write(type=4, len=22) -> 0
16:42:05.487567 [0-0] <= Recv header, 24 bytes (0x18)
0000: Connection: keep-alive
16:42:05.497376 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=24) -> 0
16:42:05.507357 [0-0] == Info: [WRITE] cw_out, wrote 24 header bytes -> 24
16:42:05.514712 [0-0] == Info: [WRITE] download_write header(type=4, blen=24) -> 0
16:42:05.527094 [0-0] == Info: [WRITE] client_write(type=4, len=24) -> 0
16:42:05.537182 [0-0] <= Recv header, 44 bytes (0x2c)
0000: Cache-Control: public, max-age=0, no-cache
16:42:05.552540 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=44) -> 0
16:42:05.560818 [0-0] == Info: [WRITE] cw_out, wrote 44 header bytes -> 44
16:42:05.570322 [0-0] == Info: [WRITE] download_write header(type=4, blen=44) -> 0
16:42:05.578929 [0-0] == Info: [WRITE] client_write(type=4, len=44) -> 0
16:42:05.587563 [0-0] <= Recv header, 23 bytes (0x17)
0000: X-Frame-Options: DENY
16:42:05.596820 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=23) -> 0
16:42:05.606906 [0-0] == Info: [WRITE] cw_out, wrote 23 header bytes -> 23
16:42:05.614386 [0-0] == Info: [WRITE] download_write header(type=4, blen=23) -> 0
16:42:05.625475 [0-0] == Info: [WRITE] client_write(type=4, len=23) -> 0
16:42:05.634063 [0-0] <= Recv header, 43 bytes (0x2b)
0000: Strict-Transport-Security: max-age=604800
16:42:05.645196 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=43) -> 0
16:42:05.653645 [0-0] == Info: [WRITE] cw_out, wrote 43 header bytes -> 43
16:42:05.661868 [0-0] == Info: [WRITE] download_write header(type=4, blen=43) -> 0
16:42:05.673236 [0-0] == Info: [WRITE] client_write(type=4, len=43) -> 0
16:42:05.681299 [0-0] <= Recv header, 2 bytes (0x2)
0000:
16:42:05.688896 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=2) -> 0
16:42:05.697937 [0-0] == Info: [WRITE] cw_out, wrote 2 header bytes -> 2
16:42:05.710009 [0-0] == Info: [WRITE] download_write header(type=4, blen=2) -> 0
16:42:05.725276 [0-0] == Info: [WRITE] client_write(type=4, len=2) -> 0
16:42:05.741201 [0-0] <= Recv data, 1012 bytes (0x3f4)
0000: {. "gU8sMLbIXS0": "https://community.letsencrypt.org/t/adding-r
0040: andom-entries-to-the-directory/33417",. "keyChange": "https://a
0080: cme-v02.api.letsencrypt.org/acme/key-change",. "meta": {. "c
00c0: aaIdentities": [. "letsencrypt.org". ],. "profiles":
0100: {. "classic": "Profiles - Let's Encrypt
0140: c",. "shortlived": "https://letsencrypt.org/docs/profiles#s
0180: hortlived (not yet generally available)",. "tlsserver": "ht
01c0: tps://letsencrypt.org/docs/profiles#tlsserver". },. "terms
0200: OfService": "https://letsencrypt.org/documents/LE-SA-v1.5-Februa
0240: ry-24-2025.pdf",. "website": "https://letsencrypt.org". },.
0280: "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-ac
02c0: ct",. "newNonce": "https://acme-v02.api.letsencrypt.org/acme/ne
0300: w-nonce",. "newOrder": "https://acme-v02.api.letsencrypt.org/ac
0340: me/new-order",. "renewalInfo": "https://acme-v02.api.letsencryp
0380: t.org/draft-ietf-acme-ari-03/renewalInfo",. "revokeCert": "http
03c0: s://acme-v02.api.letsencrypt.org/acme/revoke-cert".}
{
"gU8sMLbIXS0": "Adding random entries to the directory",
"keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
"meta": {
"caaIdentities": [
"letsencrypt.org"
],
"profiles": {
"classic": "Profiles - Let's Encrypt",
"shortlived": "Profiles - Let's Encrypt (not yet generally available)",
"tlsserver": "https://letsencrypt.org/docs/profiles#tlsserver"
},
"termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf",
"website": "https://letsencrypt.org"
},
"newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
"newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
"newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
"renewalInfo": "https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-03/renewalInfo",
"revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}16:42:06.017141 [0-0] == Info: [WRITE] cw_out, wrote 1012 body bytes -> 1012
16:42:06.031059 [0-0] == Info: [WRITE] download_write body(type=1, blen=1012) -> 0
16:42:06.047276 [0-0] == Info: [WRITE] client_write(type=1, len=1012) -> 0
16:42:06.061393 [0-0] == Info: [WRITE] xfer_write_resp(len=1271, eos=0) -> 0
16:42:06.078196 [0-0] == Info: [WRITE] cw-out is notpaused
16:42:06.090406 [0-0] == Info: [WRITE] cw-out done
16:42:06.099406 [0-0] == Info: [READ] client_reset, clear readers
16:42:06.113054 [0-0] == Info: Connection #0 to host acme-v02.api.letsencrypt.org left intact

I think this problem could be solved by upgrading certbot. (I am not sure, though, because I don't know what version of requests came with certbot 0.40.0, and it can depend on how it's been packaged and how you installed it)

Requests uses certificates from the package certifi. This allows for users to update their trusted certificates without changing the version of Requests.

Before version 2.16, Requests bundled a set of root CAs that it trusted, sourced from the Mozilla trust store. The certificates were only updated once for each Requests version. When certifi was not installed, this led to extremely out-of-date certificate bundles when using significantly older versions of Requests.

For the sake of security we recommend upgrading certifi frequently!

2 Likes

Thanks 9peppe. I don't have info on any of those questions either. Do you know how I could update the certbot?

1 Like

You could try using acme.sh instead as your curl works but your python doesn't.

The root of the issue is most likely that various components are just too old. Your certbot is from 2016 and likely your python version is as well, modern versions of certbot install using snap and I'm not sure if your system supports that or not.

4 Likes

Their Ubuntu 20 should support snap. It is pre-installed on Ubuntu starting with 16.04 LTS.

@ithelpdesksg Follow the instructions below carefully to upgrade Certbot. It may not fix your problem but is good place to start.

4 Likes

Hi @MikeMcQ ,

Thanks for the instructions. Upgrading to the certbot from SNAP helps but I notice 2 items:

  1. it seem to create a new cert called "adanisingapore.com.sg-0001".
  2. the auto renew works for all 3 certs as below but the site is still showing the expiring old cert. My guess is the site needs to be updated with the new cert but that's what I'd been struggling to do. Is there any way to renew the old cert w/o needing to update the site? Thanks.
Found the following certs:
  Certificate Name: adanisingapore.com.sg-0001
    Serial Number: <removed for privacy reason>
    Key Type: ECDSA
    Domains: adanisingapore.com.sg
    Expiry Date: 2025-08-03 05:27:22+00:00 (VALID: 88 days)
    Certificate Path: /etc/letsencrypt/live/adanisingapore.com.sg-0001/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/adanisingapore.com.sg-0001/privkey.pem

  Certificate Name: adanisingapore.com.sg
    Serial Number: <removed for privacy reason>
    Key Type: RSA
    Domains: adanisingapore.com.sg www.adanisingapore.com.sg
    Expiry Date: 2025-08-03 21:46:06+00:00 (VALID: 88 days)
    Certificate Path: /etc/letsencrypt/live/adanisingapore.com.sg/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/adanisingapore.com.sg/privkey.pem

  Certificate Name: www.adanisingapore.com.sg
    Serial Number: <removed for privacy reason>
    Key Type: RSA
    Domains: www.adanisingapore.com.sg
    Expiry Date: 2025-08-03 05:34:10+00:00 (VALID: 88 days)
    Certificate Path: /etc/letsencrypt/live/www.adanisingapore.com.sg/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/www.adanisingapore.com.sg/privkey.pem

You probably just need to reload your LiteSpeed server so it sees the new cert

You don't need 3 certs. This one has both your domain names in it and is all you need

Certificate Name: adanisingapore.com.sg
    Serial Number: <removed for privacy reason>
    Key Type: RSA
    Domains: adanisingapore.com.sg www.adanisingapore.com.sg
    Certificate Path: /etc/letsencrypt/live/adanisingapore.com.sg/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/adanisingapore.com.sg/privkey.pem

Which of these certificate files is your LiteSpeed server configured to use? We should delete the certificates that you don't use. It is wasteful and can cause other problems.

We can also update your Certbot renewal config so it automatically reloads LiteSpeed. I'll describe how once you explain which certificates you can delete.

sudo certbot delete --cert-name X

Where X is the Certificate Name from the certificates list.

2 Likes

Hi @MikeMcQ,

Thanks. I removed the unnecessary certs and it's working now.

I need to use "sudo /usr/local/lsws/bin/lswsctrl restart" to reload the site.
Do I include this in the certbot file in "cron.d" or conf file in "/etc/letsencrypt/renewal/" to automatically reload LiteSpeed after the cert renewal?

--deploy-hook DEPLOY_HOOK
                        Command to be run in a shell once for each
                        successfully issued certificate. Unless --disable-
                        hook-validation is used, the command’s first word must
                        be the absolute pathname of an executable or one found
                        via the PATH environment variable. For this command,
                        the shell variable $RENEWED_LINEAGE will point to the
                        config live subdirectory (for example,
                        "/etc/letsencrypt/live/example.com") containing the
                        new certificates and keys; the shell variable
                        $RENEWED_DOMAINS will contain a space-delimited list
                        of renewed certificate domains (for example,
                        "example.com www.example.com") (default: None)

Something like certbot reconfigure --cert-name $CERTNAME --deploy-hook "some command" should work.

4 Likes

Looks like this would work. Thanks @9peppe

2 Likes