Polynomial World

Engineering

Free SSL on cPanel with acme.sh and Let's Encrypt (Step by Step)

Polynomial World 9 min read

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_uapi hook 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.com and www.example.com: your domain
  • cpaneluser: your cPanel username
  • /home/cpaneluser/example.com: the domain’s document root (yours may be public_html or 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.com and www.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 -w in Step 4.
  • ip: the domain’s IP address on the server.
  • serveralias: a space-separated list of aliases. Check that www.example.com is 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:

  1. Copies acme.sh to ~/.acme.sh/. Your certificates are stored there too.
  2. Creates an alias so you can type acme.sh.
  3. 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.com and www.example.com resolve 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_client shows the new issuer and dates for both names
  • crontab -l shows the acme.sh --cron line, and you have a backup of your crontab

Related articles

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.

8 min read