Engineering
Free SSL on cPanel with acme.sh and Let's Encrypt (Step by Step)
You can get a free Let’s Encrypt certificate on shared cPanel hosting with acme.sh, a shell script that runs under your normal cPanel user over SSH. You issue the certificate with the webroot method, install it with acme.sh’s cpanel_uapi deploy hook, and let acme.sh’s cron job renew it. This guide follows a setup we did on a shared cPanel account. All names below are placeholders.
Key facts (as of October 2026)
- acme.sh’s default CA is ZeroSSL, not Let’s Encrypt. Switch it with
--set-default-ca --server letsencrypt(acme.sh wiki).- New certificates are ECC by default (acme.sh README).
- The
cpanel_uapihook needs cPanel & WHM version 56 or newer (acme.sh wiki: deploy hooks).- Let’s Encrypt certificates are valid for 90 days by default (Let’s Encrypt FAQ). acme.sh renews them automatically.
- acme.sh doesn’t need root (acme.sh README).
Placeholders used in this guide:
example.comandwww.example.com: your domaincpaneluser: your cPanel username/home/cpaneluser/example.com: the domain’s document root (yours may bepublic_htmlor something else)you@example.com: your email
What do you need before you start?
- SSH access to your cPanel account. Many shared hosts offer it, but some turn it off by default.
- A domain whose DNS already points to this server, for both
example.comandwww.example.com. - The domain added to your cPanel account as the main domain or an addon domain.
If your host already offers free certificates in its control panel, check that first. You may not need this guide.
Step 1: Check DNS and find the document root
The webroot method works by placing a file in your site’s folder. Let’s Encrypt then fetches it over HTTP at http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>, on port 80 (Let’s Encrypt: challenge types). So both names must resolve to this server, and you need the exact folder.
Ask cPanel for the domain’s settings with UAPI:
uapi --output=jsonpretty DomainInfo single_domain_data domain=example.com
Look at these fields in the output (cPanel API docs):
documentroot: the absolute path to the domain’s document root. You’ll use it with-win Step 4.ip: the domain’s IP address on the server.serveralias: a space-separated list of aliases. Check thatwww.example.comis there.
Then compare the ip with what public DNS returns:
dig +short example.com
dig +short www.example.com
If the addresses don’t match, fix DNS first and wait for the change to spread. Note that single_domain_data can’t query a parked domain (an alias). Ask for the main or addon domain instead.
Step 2: Install acme.sh
Run the official installer with your email:
curl https://get.acme.sh | sh -s email=you@example.com
According to the acme.sh README, the installer does three things:
- Copies acme.sh to
~/.acme.sh/. Your certificates are stored there too. - Creates an alias so you can type
acme.sh. - Creates a cron job to check and renew certificates.
Close your SSH session and open a new one so the alias takes effect. If the command isn’t found, run ~/.acme.sh/acme.sh instead.
Step 3: Switch the default CA to Let’s Encrypt
As of acme.sh v3.0, the default CA is ZeroSSL (acme.sh wiki). Both are free. We chose Let’s Encrypt, so we changed the default:
acme.sh --set-default-ca --server letsencrypt
The wiki notes that existing certificates keep renewing with the CA that issued them. Changing the default only affects new certificates.
Step 4: Issue the certificate for the apex and www
Use the document root from Step 1:
acme.sh --issue -d example.com -d www.example.com -w /home/cpaneluser/example.com
The first -d is the main domain. Each extra -d adds a name to the same certificate. The README says you must have write access to the webroot folder, and all the names must point to that same folder (acme.sh README).
When it succeeds, acme.sh prints the paths of the certificate files. Because certificates are ECC by default, they go in ~/.acme.sh/example.com_ecc/.
Step 5: Install the certificate in cPanel with the cpanel_uapi hook
acme.sh --deploy -d example.com --ecc --deploy-hook cpanel_uapi
--ecc tells acme.sh to use the ECC certificate from Step 4. Current versions also detect it on their own when only an ECC certificate exists, but passing the flag removes any doubt (acme.sh source).
You don’t need extra settings when you run this as your cPanel user. DEPLOY_CPANEL_USER is only required if you run the script as root (acme.sh wiki: deploy hooks).
By default, the hook installs the certificate only on cPanel sites whose names match the domains in the certificate. Other sites on the same account are left alone (cpanel_uapi hook source).
Step 6: Verify the certificate the server returns
Check from your own computer or the server:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -subject -dates
-servername sends the hostname so the server picks the right certificate (OpenSSL s_client docs). In the output, the issuer should name Let’s Encrypt and notAfter should be about 90 days away. Repeat with www.example.com.
You can also open the site in a browser and view the certificate details from the padlock icon.
How does renewal work?
The README describes the cron job as a daily check. In the current source (v3.1.6), the installer schedules it four times a day, six hours apart, at a random minute (acme.sh source). Most runs do nothing. A certificate is renewed only when one of these is true (acme.sh README):
- You pass
--force. - The CA’s renewal window (ARI) has started.
- The saved next-renewal time has passed. Without ARI, that’s every 30 days.
When you ran --deploy, acme.sh saved the hook name with the certificate’s settings. After a successful renewal, it runs the same hook again, so the new certificate goes into cPanel without any action from you (acme.sh source).
To see your certificates and their next renewal dates:
acme.sh --list
Then confirm the cron entry exists:
crontab -l
You should see a line that runs acme.sh --cron.
What can go wrong?
DNS doesn’t point to the host. Let’s Encrypt fetches the challenge file from whatever server DNS returns. If www points somewhere else, or a CDN serves the domain, the check fails. Fix DNS, or leave that name out of the certificate.
HTTP to HTTPS redirects. These are usually fine. Let’s Encrypt “follows redirects, up to 10 redirects deep”, only to ports 80 or 443. When redirected to HTTPS, it doesn’t validate the certificate, so an expired or self-signed one won’t block the check (Let’s Encrypt: challenge types). Problems start when a redirect sends the request to a different host or folder where the challenge file doesn’t exist.
Host restrictions. Some shared hosts disable SSH or the uapi command, or block access to .well-known. The deploy hook stops with “The command uapi is not found” if uapi is missing (cpanel_uapi hook source). If you hit these limits, ask your host or use their built-in SSL option.
Editing your crontab and losing it. This one happened to us. We tried to change one cron line with a pipe like this:
# Don't do this
crontab -l | sed 's/old/new/' | crontab -
crontab - replaces your whole crontab with whatever arrives on standard input (crontab man page). If the left side fails or prints nothing, you install an empty crontab. Our crontab was wiped, including the acme.sh renewal job. acme.sh’s own installer guards against the same risk. It refuses to install its cron line if it can’t read your current crontab, because piping an incomplete list back “would wipe the user’s existing cron jobs” (acme.sh source).
Do it in three steps instead:
crontab -l > ~/crontab.backup # 1. back up
cp ~/crontab.backup ~/crontab.new
nano ~/crontab.new # 2. edit the copy
crontab ~/crontab.new # 3. install from the file
crontab -l # check the result
If anything looks wrong, restore with crontab ~/crontab.backup.
Deploying to the wrong place. The hook matches sites by the names in the certificate. If a site isn’t covered, check that its name is in the certificate and that it’s on the same cPanel account.
Short FAQ
Do I need root? No. acme.sh runs as a normal user, and the cpanel_uapi hook works as your cPanel user (acme.sh README).
Can I go back to ZeroSSL later? Yes. Pass --server with the CA you want when you issue, or change the default again. Existing certificates keep their current CA until reissued (acme.sh wiki).
How do I stop renewing a certificate? Run acme.sh --remove -d example.com --ecc. This removes it from the renewal list (acme.sh README).
Checklist
-
example.comandwww.example.comresolve to the server’s IP - You know the exact document root
- acme.sh installed with your email
- Default CA set to Let’s Encrypt
- Certificate issued for both names with
-w - Deployed with
--ecc --deploy-hook cpanel_uapi -
openssl s_clientshows the new issuer and dates for both names -
crontab -lshows theacme.sh --cronline, and you have a backup of your crontab
Related articles
Engineering
Build a Landing Page with Claude Code, Step by Step
Install Claude Code, write a clear brief, build a static landing page, check mobile and accessibility, add analytics, and deploy it.
Engineering
Deploy Astro to Shared Hosting with GitHub Actions (FTPS)
Push to main, build your Astro site in GitHub Actions, and upload dist/ to shared hosting over FTPS. Secrets, variables, skips, re-runs, and fixes.
Engineering
What Vibe Coding Is, and Where It Breaks
Vibe coding means prompting an AI and accepting its code unread. See where it works, where it breaks, and what to check before you ship.