Background

If you run a website or personal blog, you need to keep track of your SSL certificate expiration every year. Recently, the SSL certificate for wnote.com is also approaching its expiry.

Obtaining an SSL certificate enables HTTPS access for your site. There are both free and paid options available. Major domestic cloud providers like Alibaba Cloud, Tencent Cloud, and UCloud offer free SSL certificate services, typically valid for one year and requiring manual renewal. If your audience is primarily overseas, consider using Cloudflare’s CDN with built-in free SSL protection.

For those who enjoy tinkering, Let’s Encrypt offers free certificates. Visit https://letsencrypt.org/ for more details. The official recommendation is to use the Certbot client, which supports the ACME protocol—though we won’t cover it here in depth.

In this guide, we’ll use acme.sh to issue free certificates. The main reasons are:

  • Full support for the ACME protocol.
  • Support for wildcard domain certificates.
  • Automatic renewal capability.

By default, acme.sh issues certificates from https://zerossl.com, with no usage limits. However, if you apply directly via ZeroSSL’s website, you’re limited to three free certificates.

Project repository:

https://github.com/acmesh-official/acme.sh


Install ACME

After installation, the default directory is ~/.acme.sh/, where all certificates will be stored.

1
2
3
git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install -m my@example.com # Replace with your email

The installer performs three actions:

  • Creates and copies acme.sh to your home directory ($HOME): ~/.acme.sh/.
  • Sets up an alias: acme.sh=~/.acme.sh/acme.sh.
  • Optionally creates a daily cron job to check and update certificates.

Issue Certificates

HTTP Challenge (Webroot Method)

For a single domain:

1
acme.sh --issue -d www.example.com -w /home/wwwroot/www.example.com

Or:

1
acme.sh --issue -d example.com -w /home/username/public_html

Or:

1
acme.sh --issue -d example.com -w /var/www/html

For multiple domains in one certificate:

1
acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com
  • The -d parameter specifies the domain(s) for which the certificate is issued. At least one domain is required, and it must correspond to the web root specified by -w.
  • The path after -w (e.g., /home/wwwroot/example.com, /var/www/html) is your website’s web root directory. You must have write permissions here, as acme.sh will create a .well-known directory and place verification files inside to prove ownership.

Certificates are stored by default in ~/.acme.sh/example.com/ and are automatically renewed every 60 days.


DNS API Challenge (Automatic)

If your DNS provider supports API access, you can automate certificate issuance without any manual steps!

Supported DNS providers: https://github.com/acmesh-official/acme.sh/wiki/dnsapi

Since wnote.com uses Alibaba Cloud DNS, and it’s on the supported list, we’ll use the DNS API method.

First, generate an AccessKey pair in Alibaba Cloud RAM Console:
https://ram.console.aliyun.com/manage/ak

After creation, record your AccessKey ID and AccessKey Secret for later use. These credentials will be saved in ~/.acme.sh/account.conf after successful issuance, enabling automatic renewal via cron.

1
2
3
root@VM-0-3-ubuntu:~# export Ali_Key="LTII5t8tE4IgMFGDK6iTcs2A"
root@VM-0-3-ubuntu:~# export Ali_Secret="Ku7q3lPJMISlJqZ9OomLOfzO8LFVff"
root@VM-0-3-ubuntu:~# acme.sh --issue --dns dns_ali -d wnote.com -d *.wnote.com

Wait for the process to complete (~2 minutes). Example log output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[Fri 11 Nov 2022 10:40:31 PM CST] Using CA: https://acme.zerossl.com/v2/DV90
[Fri 11 Nov 2022 10:40:31 PM CST] Multi domain='DNS:wnote.com,DNS:*.wnote.com'
[Fri 11 Nov 2022 10:40:31 PM CST] Getting domain auth token for each domain
[Fri 11 Nov 2022 10:40:34 PM CST] Could not get nonce, let's try again.
[Fri 11 Nov 2022 10:40:41 PM CST] Could not get nonce, let's try again.
[Fri 11 Nov 2022 10:40:47 PM CST] Could not get nonce, let's try again.
[Fri 11 Nov 2022 10:41:38 PM CST] Getting webroot for domain='wnote.com'
[Fri 11 Nov 2022 10:41:38 PM CST] Getting webroot for domain='*.wnote.com'
[Fri 11 Nov 2022 10:41:38 PM CST] Adding txt value: 9QXV-Ve3eEI-JCLjJ7RkMMvPGNaTzV3YmlaXWtwrJVM for domain:  _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:41:40 PM CST] The txt record is added: Success.
[Fri 11 Nov 2022 10:41:40 PM CST] Adding txt value: KXKFA3BChlz0c5NTHN4fmO8jo-e3DbMu0VybF-YohTw for domain:  _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:41:42 PM CST] The txt record is added: Success.
[Fri 11 Nov 2022 10:41:42 PM CST] Let's check each DNS record now. Sleep 20 seconds first.
[Fri 11 Nov 2022 10:42:03 PM CST] You can use '--dnssleep' to disable public dns checks.
[Fri 11 Nov 2022 10:42:03 PM CST] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck
[Fri 11 Nov 2022 10:42:03 PM CST] Checking wnote.com for _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:42:06 PM CST] Domain wnote.com '_acme-challenge.wnote.com' success.
[Fri 11 Nov 2022 10:42:06 PM CST] Checking wnote.com for _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:42:07 PM CST] Domain wnote.com '_acme-challenge.wnote.com' success.
[Fri 11 Nov 2022 10:42:07 PM CST] All success, let's return
[Fri 11 Nov 2022 10:42:07 PM CST] Verifying: wnote.com
[Fri 11 Nov 2022 10:42:12 PM CST] Processing, The CA is processing your order, please just wait. (1/30)
[Fri 11 Nov 2022 10:42:20 PM CST] Processing, The CA is processing your order, please just wait. (2/30)
[Fri 11 Nov 2022 10:42:29 PM CST] Processing, The CA is processing your order, please just wait. (3/30)
[Fri 11 Nov 2022 10:42:38 PM CST] Processing, The CA is processing your order, please just wait. (4/30)
[Fri 11 Nov 2022 10:42:46 PM CST] Processing, The CA is processing your order, please just wait. (5/30)
[Fri 11 Nov 2022 10:42:55 PM CST] Processing, The CA is processing your order, please just wait. (6/30)
[Fri 11 Nov 2022 10:43:02 PM CST] Success
[Fri 11 Nov 2022 10:43:02 PM CST] Verifying: *.wnote.com
[Fri 11 Nov 2022 10:43:06 PM CST] Processing, The CA is processing your order, please just wait. (1/30)
[Fri 11 Nov 2022 10:43:13 PM CST] Processing, The CA is processing your order, please just wait. (2/30)
[Fri 11 Nov 2022 10:43:28 PM CST] Processing, The CA is processing your order, please just wait. (3/30)
[Fri 11 Nov 2022 10:43:41 PM CST] Processing, The CA is processing your order, please just wait. (4/30)
[Fri 11 Nov 2022 10:43:48 PM CST] Processing, The CA is processing your order, please just wait. (5/30)
[Fri 11 Nov 2022 10:43:52 PM CST] Processing, The CA is processing your order, please just wait. (6/30)
[Fri 11 Nov 2022 10:43:58 PM CST] Processing, The CA is processing your order, please just wait. (7/30)
[Fri 11 Nov 2022 10:44:10 PM CST] Processing, The CA is processing your order, please just wait. (8/30)
[Fri 11 Nov 2022 10:44:29 PM CST] Processing, The CA is processing your order, please just wait. (9/30)
[Fri 11 Nov 2022 10:44:59 PM CST] Success
[Fri 11 Nov 2022 10:44:59 PM CST] Removing DNS records.
[Fri 11 Nov 2022 10:44:59 PM CST] Removing txt: 9QXV-Ve3eEI-JCLmJ7RkLMvPGNaTzV3YmlaXWtwrJVM for domain: _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:45:02 PM CST] Removed: Success
[Fri 11 Nov 2022 10:45:02 PM CST] Removing txt: KXKKA3BChlz0c5NOHN4fmk8jo-e3DMMu0VybF-YohTw for domain: _acme-challenge.wnote.com
[Fri 11 Nov 2022 10:45:06 PM CST] Removed: Success
[Fri 11 Nov 2022 10:45:06 PM CST] Verify finished, start to sign.
[Fri 11 Nov 2022 10:45:06 PM CST] Lets finalize the order.
[Fri 11 Nov 2022 10:45:06 PM CST] Le_OrderFinalize='https://acme.zerossl.com/v2/DV90/order/FYF3wr0jlkJarp3dFuRaKg/finalize'
[Fri 11 Nov 2022 10:45:33 PM CST] Order status is processing, lets sleep and retry.
[Fri 11 Nov 2022 10:45:33 PM CST] Retry after: 15
[Fri 11 Nov 2022 10:45:49 PM CST] Polling order status: https://acme.zerossl.com/v2/DV90/order/FYF3wr0jlkJarp3dFuRaKg
[Fri 11 Nov 2022 10:45:55 PM CST] Downloading cert.
[Fri 11 Nov 2022 10:45:55 PM CST] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/RVtakD091ssrWC6HxgcWxQ'
[Fri 11 Nov 2022 10:46:02 PM CST] Cert success.
-----BEGIN CERTIFICATE-----
MIIGbjCCBFagAwIBAgIRAOee+mMTTeRu9hUkpcDXkQcwDQYJKoZIhvcNAQEMBQAw
SzELMAkGA1UEBhMCQVQxEDAOBgNVBAoTB1plcm9TU0wxKjAoBgNVBAMTIVZlcm9T
U0wgUlNBIERvbWFpbiBTZWN1cmUgU2l0ZSBDQTAeFw0yMjExMTEwMDAwMDBaFw0y
......
......
fH91qX3O1eXUPQEku+ZLx8hfqKKUuI0l8t6qQkDnK3N+987XdsEXN2mRHnTOfyo4
4Z8RVsC8gXep8VxsVSQ/0urED3ghLBz7Ya5pLFl0inJeLXC/MD1HETryH1iSojv2
JHNsJhGlwrhqORg91jodBBl4
-----END CERTIFICATE-----
[Fri 11 Nov 2022 10:46:02 PM CST] Your cert is in: /root/.acme.sh/wnote.com/wnote.com.cer
[Fri 11 Nov 2022 10:46:02 PM CST] Your cert key is in: /root/.acme.sh/wnote.com/wnote.com.key
[Fri 11 Nov 2022 10:46:02 PM CST] The intermediate CA cert is in: /root/.acme.sh/wnote.com/ca.cer
[Fri 11 Nov 2022 10:46:02 PM CST] And the full chain certs is there: /root/.acme.sh/wnote.com/fullchain.cer

Manual DNS Challenge

If your DNS provider isn’t supported by acme.sh, manually add TXT records:

1
2
3
4
5
Domain: _acme-challenge.example.com
TXT Value: 9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c

Domain: _acme-challenge.www.example.com
TXT Value: 9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Then issue the certificate:

1
acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com

Update CA Certificate Authority (CA)

Currently, acme.sh supports four official CAs: Let’s Encrypt, Buypass, ZeroSSL, and SSL.com. By default, it uses ZeroSSL.

To switch CA, use:

1
2
3
4
5
acme.sh --set-default-ca --server letsencrypt
acme.sh --set-default-ca --server buypass
acme.sh --set-default-ca --server zerossl
acme.sh --set-default-ca --server ssl.com
acme.sh --set-default-ca --server google

⚠️ Note: Avoid using Google Public CA for domestic websites, as many domains fail validation due to network restrictions.

Google Public CA tutorial: https://cloud.google.com/certificate-manager/docs/public-ca-tutorial


Update Docker & Nginx Configuration

After obtaining the certificate, update your docker-compose.yml and nginx.conf.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: "3"
services:
  nginx:
    image: nginx:1.18.0
    volumes:
      - ./conf/nginx.conf:/etc/nginx/nginx.conf
      - /opt/wwwroot/wnote.com:/opt/wnote.com
      - /root/.acme.sh/wnote.com:/opt/ssl
      - ./logs:/var/log/nginx
    restart: always
    ports:
      - 443:443
      - 80:80
    networks:
      - my-nginx
networks:
  my-nginx:
    driver: bridge
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
    listen       443 ssl http2;
    server_name  wnote.com www.wnote.com;
    root         /opt/wnote.com;
    ssl_certificate      /opt/ssl/wnote.com.cer;
    ssl_certificate_key  /opt/ssl/wnote.com.key;
    ssl_session_timeout  5m;
    ssl_session_cache    shared:SSL:1m;
    ssl_ciphers          ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:aNULL:!MD5:!ADH:!RC4;
    ssl_protocols        TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers  on;

    location / {
        root   /opt/wnote.com;
        index  index.html index.htm;
    }
}

server {
    listen 80;
    server_name wnote.com;
    rewrite ^ https://$host$1 permanent;
}

That concludes our guide on issuing and automatically renewing free SSL certificates (including wildcard domains) using acme.sh.