How to apply for a free wildcard SSL certificate with cloudflare API on Ubuntu

Background

1) plaintext transmission is no longer acceptable. For example, you can not open a website prefixed http.
2) Self-signed is not suitable for public demonstration, though 10-year term, and not well recognized by web browsers, too.
3) Cyber security should be enhanced is the best practice when you are exposed on the interne.

Why wildcard certificates

1) Handy integrated tools available to get such certificates issued by a bunch of certificate authorities
2) Streamlined configurations on Nginx when there are subdomains under a main domain.

Requisites

Before this tutorial, I assume you have some knowledge about how to add DNS records on your Cloudflare web control panel and some experience using Linux command lines. 
1) A virtual/ dedicated server
2) A domain name to which your VPS IP points
3) Some DNS records kept under your domain name on Cloudflare, like A, CNAME, TXT records.

Steps

1. Get the latest version of acme.sh

curl https://get.acme.sh |sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1032 0 1032 0 0 5708 0 --:--:-- --:--:-- --:--:-- 5733
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 221k 100 221k 0 0 697k 0 --:--:-- --:--:-- --:--:-- 695k
[Sat Apr 12 06:35:19 AM CEST 2025] Installing from online archive.
[Sat Apr 12 06:35:19 AM CEST 2025] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
[Sat Apr 12 06:35:20 AM CEST 2025] Extracting master.tar.gz
[Sat Apr 12 06:35:20 AM CEST 2025] It is recommended to install socat first.
[Sat Apr 12 06:35:20 AM CEST 2025] We use socat for the standalone server, which is used for standalone mode.
[Sat Apr 12 06:35:20 AM CEST 2025] If you don't want to use standalone mode, you may ignore this warning.
[Sat Apr 12 06:35:20 AM CEST 2025] Installing to /root/.acme.sh
[Sat Apr 12 06:35:20 AM CEST 2025] Installed to /root/.acme.sh/acme.sh
[Sat Apr 12 06:35:20 AM CEST 2025] Installing alias to '/root/.bashrc'
[Sat Apr 12 06:35:20 AM CEST 2025] Close and reopen your terminal to start using acme.sh
[Sat Apr 12 06:35:20 AM CEST 2025] Installing cron job
no crontab for root
no crontab for root
[Sat Apr 12 06:35:20 AM CEST 2025] bash has been found. Changing the shebang to use bash as preferred.
[Sat Apr 12 06:35:21 AM CEST 2025] OK
[Sat Apr 12 06:35:21 AM CEST 2025] Install success!

2. Install socat as recommended by acme

root@vmi2529283:/# apt install socat -y
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  linux-image-5.4.0-105-generic linux-modules-5.4.0-105-generic
Use 'apt autoremove' to remove them.
The following NEW packages will be installed:
  socat
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 349 kB of archives.
After this operation, 1,381 kB of additional disk space will be used.
Get:1 http://asi-fs-n.contabo.net/ubuntu jammy/main amd64 socat amd64 1.7.4.1-3ubuntu4 [349 kB]
Fetched 349 kB in 0s (9,757 kB/s)
Selecting previously unselected package socat.
(Reading database ... 104015 files and directories currently installed.)
Preparing to unpack .../socat_1.7.4.1-3ubuntu4_amd64.deb ...
Unpacking socat (1.7.4.1-3ubuntu4) ...
Setting up socat (1.7.4.1-3ubuntu4) ...
Processing triggers for man-db (2.10.2-1) ...

3. Run the acme.sh script again

root@vmi2529283:/# curl https://get.acme.sh |sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1032    0  1032    0     0   7362      0 --:--:-- --:--:-- --:--:--  7371
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  221k  100  221k    0     0  1018k      0 --:--:-- --:--:-- --:--:-- 1019k
[Sat Apr 12 06:36:15 AM CEST 2025] Installing from online archive.
[Sat Apr 12 06:36:15 AM CEST 2025] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
[Sat Apr 12 06:36:16 AM CEST 2025] Extracting master.tar.gz
[Sat Apr 12 06:36:16 AM CEST 2025] Installing to /root/.acme.sh
[Sat Apr 12 06:36:16 AM CEST 2025] Installed to /root/.acme.sh/acme.sh
[Sat Apr 12 06:36:16 AM CEST 2025] Installing alias to '/root/.bashrc'
[Sat Apr 12 06:36:16 AM CEST 2025] Close and reopen your terminal to start using acme.sh
[Sat Apr 12 06:36:16 AM CEST 2025] Installing cron job
20 11 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
[Sat Apr 12 06:36:16 AM CEST 2025] bash has been found. Changing the shebang to use bash as preferred.
[Sat Apr 12 06:36:17 AM CEST 2025] OK
[Sat Apr 12 06:36:17 AM CEST 2025] Install success!

4. Alias /root/.acme.sh/acme.sh to acme.sh by appending “alias acme.sh=~/.acme.sh/acme.sh”

root@vmi2529283:/# vim /root/.bashrc
root@vmi2529283:/# source /root/.bashrc

5. Check version

root@vmi2529283:/# acme.sh --version
https://github.com/acmesh-official/acme.sh
v3.1.1

6. We need conf file to store Cloudflare global APAI key that is a mixed series of numbers and alphabets, which can be acquired in your profile on Cloudflare

root@vmi2529283:/# vim /root/.acme.sh/cf_account.conf
###  Replace your own key and Email address used for cloudflare
SAVED_CF_Key='34xxxxxxxxxxx617998c5e5f5afxxxx2xfb'
SAVED_CF_Email='xxxxxxxxxx@xxxxxx.com'
### the third line is automatically added by acme.sh when you apply for the wildcard certificate
USER_PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin'

7. Register with your own Email in the ZeroSSL

root@vmi2529283:/# acme.sh --register-account -m xxxxxxxxxx@xxxx.com
[Sat Apr 12 06:47:27 AM CEST 2025] No EAB credentials found for ZeroSSL, let's obtain them
[Sat Apr 12 06:47:28 AM CEST 2025] Registering account: https://acme.zerossl.com/v2/DV90
[Sat Apr 12 06:47:31 AM CEST 2025] Registered
[Sat Apr 12 06:47:31 AM CEST 2025] ACCOUNT_THUMBPRINT='lKQ2UJqCY2mKjaxxxxxxxxxxGnarghbNxxxxxxxxxxh0'
root@vmi2529283:/# acme.sh --issue --dns dns_cf -d xxxxxxxxxx.com -d *.xxxxxxxxxx.com
[Sat Apr 12 06:47:47 AM CEST 2025] Using CA: https://acme.zerossl.com/v2/DV90
[Sat Apr 12 06:47:47 AM CEST 2025] Creating domain key
[Sat Apr 12 06:47:47 AM CEST 2025] The domain key is here: /root/.acme.sh/xxxxxxxxxx.com_ecc/xxxxxxxxxx.com.key
[Sat Apr 12 06:47:47 AM CEST 2025] Multi domain='DNS:xxxxxxxxxx.com,DNS:*.xxxxxxxxxx.com'
[Sat Apr 12 06:47:58 AM CEST 2025] Getting webroot for domain='xxxxxxxxxx.com'
[Sat Apr 12 06:47:58 AM CEST 2025] Getting webroot for domain='*.xxxxxxxxxx.com'
[Sat Apr 12 06:47:58 AM CEST 2025] Adding TXT value: xxxxxxxxxxm2BjAQq3xxxxxxxxxxBfyQ2xxxxxxxxxxiCA for domain: _acme-challenge.xxxxxxxxxx.com
[Sat Apr 12 06:47:58 AM CEST 2025] You didn't specify a Cloudflare api key and email yet.
[Sat Apr 12 06:47:58 AM CEST 2025] You can get yours from here https://dash.cloudflare.com/profile.
[Sat Apr 12 06:47:58 AM CEST 2025] Error adding TXT record to domain: _acme-challenge.xxxxxxxxxx.com
[Sat Apr 12 06:47:58 AM CEST 2025] Please add '--debug' or '--log' to see more information.
[Sat Apr 12 06:47:58 AM CEST 2025] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh

8. According to the error report, we forgot to add into the command the config file that we have created named cf_account.conf

root@vmi2529283:/# acme.sh --accountconf /root/.acme.sh/cf_account.conf --issue --dns dns_cf -d xxxxxxxxxx.com -d *.xxxxxxxxxx.com
[Sat Apr 12 06:59:52 AM CEST 2025] Using CA: https://acme.zerossl.com/v2/DV90
[Sat Apr 12 06:59:52 AM CEST 2025] Multi domain='DNS:xxxxxxxxxx.com,DNS:*.xxxxxxxxxx.com'
[Sat Apr 12 06:59:57 AM CEST 2025] Getting webroot for domain='xxxxxxxxxx.com'
[Sat Apr 12 06:59:57 AM CEST 2025] Getting webroot for domain='*.xxxxxxxxxx.com'
[Sat Apr 12 06:59:57 AM CEST 2025] Adding TXT value: xxxxxxxxxxB_UnDuz9xxxxxxxxxxkcEYBgtXTxxxxxxxxxxJ8 for domain: _acme-challenge.xxxxxxxxxx.com
[Sat Apr 12 07:00:00 AM CEST 2025] Adding record
[Sat Apr 12 07:00:01 AM CEST 2025] Added, OK
[Sat Apr 12 07:00:01 AM CEST 2025] The TXT record has been successfully added.
[Sat Apr 12 07:00:01 AM CEST 2025] Let's check each DNS record now. Sleeping for 20 seconds first.
[Sat Apr 12 07:00:23 AM CEST 2025] You can use '--dnssleep' to disable public dns checks.
[Sat Apr 12 07:00:23 AM CEST 2025] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck
[Sat Apr 12 07:00:23 AM CEST 2025] Checking xxxxxxxxxxh.com for _acme-challenge.xxxxxxxxxx.com
[Sat Apr 12 07:00:23 AM CEST 2025] Success for domain xxxxxxxxxx.com '_acme-challenge.xxxxxxxxxx.com'.
[Sat Apr 12 07:00:23 AM CEST 2025] All checks succeeded
[Sat Apr 12 07:00:23 AM CEST 2025] xxxxxxxxxx.com is already verified, skipping dns-01.
[Sat Apr 12 07:00:23 AM CEST 2025] Verifying: *.xxxxxxxxxx.com
[Sat Apr 12 07:00:25 AM CEST 2025] Processing. The CA is processing your order, please wait. (1/30)
[Sat Apr 12 07:00:29 AM CEST 2025] Success
[Sat Apr 12 07:00:29 AM CEST 2025] Removing DNS records.
[Sat Apr 12 07:00:29 AM CEST 2025] Removing txt: oxxxxxxxxxxO0wxxxxrsO99sxxxxxxxxxxubCGxxxxxxxxxxyJ8 for domain: _acme-challenge.xxxxxxxxxx.com
[Sat Apr 12 07:00:32 AM CEST 2025] Successfully removed
[Sat Apr 12 07:00:32 AM CEST 2025] Verification finished, beginning signing.
[Sat Apr 12 07:00:32 AM CEST 2025] Let's finalize the order.
[Sat Apr 12 07:00:32 AM CEST 2025] Le_OrderFinalize='https://acme.zerossl.com/v2/DV90/order/6WPy66xxxxxxxxxxWmvw/finalize'
[Sat Apr 12 07:00:33 AM CEST 2025] Order status is 'processing', let's sleep and retry.
[Sat Apr 12 07:00:33 AM CEST 2025] Sleeping for 15 seconds then retrying
[Sat Apr 12 07:00:49 AM CEST 2025] Polling order status: https://acme.zerossl.com/v2/DV90/order/6WPxxxxxxxxxxgAhWmvw
[Sat Apr 12 07:00:50 AM CEST 2025] Downloading cert.
[Sat Apr 12 07:00:50 AM CEST 2025] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/_mYxxxxxxxxxxnawBWmQ'
[Sat Apr 12 07:01:05 AM CEST 2025] Cert success.
-----BEGIN CERTIFICATE-----
...................................BgNVBAoTB1plcm9TU0wxKjAoBgNVBAMTIVplcm9TU0wg
RUNDIERvbWFpbiBTZWN1cmUgU2l0ZSBDQTAeFw0yNTA0MTIwMDAwMDBaFw0yNTA3
MTEyMzU5NTlaMBk........................WNoLXRlY2guY29tMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEu7OAAMa8mYx5br/tHxc3jiMOAFn/RUN3lmrUfifrRI7a
Brf...................................
-----END CERTIFICATE-----
[Sat Apr 12 07:01:05 AM CEST 2025] Your cert is in: /root/.acme.sh/xxxxxxxxxx.com_ecc/xxxxxxxxxx.com.cer
[Sat Apr 12 07:01:05 AM CEST 2025] Your cert key is in: /root/.acme.sh/xxxxxxxxxxh.com_ecc/xxxxxxxxxx.com.key
[Sat Apr 12 07:01:05 AM CEST 2025] The intermediate CA cert is in: /root/.acme.sh/xxxxxxxxxx.com_ecc/ca.cer
[Sat Apr 12 07:01:05 AM CEST 2025] And the full-chain cert is in: /root/.acme.sh/xxxxxxxxxx.com_ecc/fullchain.cer

9. Add the key pairs: pub and priv, namely here is xxxx.key and xxxx.cer in a new ssl.conf in /etc/nginx/. You can name it whatever you like with .conf subfix

ssl_certificate /var/www/xxxxxxxxxx_ssl/fullchain.cer;
ssl_certificate_key /var/www/xxxxxxxxxx_ssl/privkey.key;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;

10. Next we can continue on with other configurations on Nginx/Apache, (and WordPress if you are about or have deployed), to build a website or other web applications.

I hope this tutorial helps solve your problems. Please stay tuned for my updates on more Internet things.

Conclusion

By default, acme script will automatically renew the certificates by regular checkout of cron utility. You can also edit it crontab jobs to make your own renewal schedule. 

I hope this tutorial helps solve your problems. In future, I may share tutorials on how to host multiple web sites through Nginx on one server machine. Please stay tuned for my updates on more Internet things. 

 

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top