# Automating Wildcard SSL Certificates on Synology NAS with acme.sh

URL: https://limehawk.io/reports/synology-wildcard-ssl-automation
Published: 2025-01-20
Author: Corey Watson, Managing Member
Topic: Infrastructure
Read time: 6 min

How to run acme.sh on a Synology NAS: wildcard Let's Encrypt via Cloudflare DNS-01, then the synology_dsm hook so certs survive DSM updates.

> **TL;DR** How to run acme.sh on a Synology NAS when DSM's Let's Encrypt tool cannot issue a wildcard and needs port 80 open. Install acme.sh in a home directory, issue a wildcard Let's Encrypt cert via Cloudflare DNS-01, then the synology_dsm deploy hook so certs survive DSM updates and renew into DSM. Nobody has clicked through Certificate Manager since.

## How do I install acme.sh on Synology?

Install acme.sh in a home directory on the Synology NAS, not `/root/.acme.sh`. DSM's built-in Let's Encrypt tool cannot issue a wildcard and needs port 80 open. Use Cloudflare DNS-01 for the Let's Encrypt DNS challenge, then the `synology_dsm` deploy hook so the acme.sh certificate lands in DSM and survives DSM updates.

## Why can't Synology issue a wildcard Let's Encrypt certificate?

DSM's built-in Let's Encrypt tool issues one public hostname over HTTP-01. It will not issue `*.int.example.com`, it will not validate a name that is not reachable from the internet, and it wants port 80 open to do the challenge. A Synology wildcard certificate needs an acme.sh client and a Let's Encrypt DNS challenge, not the built-in tool.

The NAS hosts internal names: DSM itself, dashboards, a reverse proxy for a few services behind the firewall. Those names needed a trusted cert with no extra hole in the edge. Cutting a cert per name in DSM meant either exposing those names on 80/443 or living with browser warnings. So we stood up acme.sh as a [network infrastructure](/services/network-infrastructure) job.

acme.sh is a shell ACME client. Cloudflare's DNS API writes the TXT record for the challenge. The `synology_dsm` deploy hook pushes the finished cert into DSM and restarts the services that use it. No Python runtime, no port 80, no clicking through Certificate Manager on renew day.

## Where should the acme.sh client live on a Synology NAS?

Not in `/root/.acme.sh`. That is the default, and it is a bad place on a Synology: DSM updates have wiped it, and it does not back up with the rest of the admin home. Install acme.sh into a sysadmin home directory and split certs from config.

```install-acme.sh
mkdir -p /var/services/homes/sysadmin/acme.sh/acme_certs
mkdir -p /var/services/homes/sysadmin/acme.sh/acme_config

curl https://get.acme.sh | sh -s email=admin@example.com \
  --home /var/services/homes/sysadmin/acme.sh/acme_config \
  --config-home /var/services/homes/sysadmin/acme.sh/acme_config \
  --cert-home /var/services/homes/sysadmin/acme.sh/acme_certs
```

`--home` is the scripts. `--config-home` is account config and logs. `--cert-home` is the issued certs. The email registers the ACME account so Let's Encrypt has somewhere to send expiry mail.

## How do you test acme.sh --staging without hitting the rate limit?

Run acme.sh `--staging` first. Production allows five certificates per registered domain per week. A fresh zone and a typo in the DNS plugin will burn that before the pipeline works.

```issue-staging.sh
CF_Token="YOUR_CLOUDFLARE_API_TOKEN" \
CF_Email="admin@example.com" \
SYNO_USE_TEMP_ADMIN=1 \
/var/services/homes/sysadmin/acme.sh/acme_config/acme.sh \
  --home /var/services/homes/sysadmin/acme.sh/acme_config \
  --cert-home /var/services/homes/sysadmin/acme.sh/acme_certs \
  --config-home /var/services/homes/sysadmin/acme.sh/acme_config \
  --staging \
  --issue \
  -d '*.int.example.com' \
  --dns dns_cf
```

`--staging` hits the untrusted Let's Encrypt staging directory. `--dns dns_cf` is the Cloudflare plugin. `CF_Token` and `CF_Email` are what the plugin uses to write the TXT record at `_acme-challenge.int.example.com`. `-d '*.int.example.com'` is the wildcard. Nothing on this NAS has to listen on port 80.

## How do you issue the production wildcard?

Same acme.sh command. Drop `--staging` and point `--server` at the production directory.

```issue-prod.sh
CF_Token="YOUR_CLOUDFLARE_API_TOKEN" \
CF_Email="admin@example.com" \
SYNO_USE_TEMP_ADMIN=1 \
/var/services/homes/sysadmin/acme.sh/acme_config/acme.sh \
  --home /var/services/homes/sysadmin/acme.sh/acme_config \
  --cert-home /var/services/homes/sysadmin/acme.sh/acme_certs \
  --config-home /var/services/homes/sysadmin/acme.sh/acme_config \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --issue \
  -d '*.int.example.com' \
  --dns dns_cf
```

Staging is `https://acme-staging-v02.api.letsencrypt.org/directory`. Production is `https://acme-v02.api.letsencrypt.org/directory`. Do not mix them. A staging cert will not be trusted in a browser. That is the point of the test.

## How do you automate SSL renewal with the synology_dsm deploy hook?

One acme.sh command, root, in Task Scheduler. acme.sh only renews when the cert has fewer than 30 days left, so a daily run is a no-op most mornings. On the morning it matters, `--renew` plus `--deploy-hook synology_dsm` installs the new cert and restarts the services that use it.

```renew-deploy.sh
CF_Token="YOUR_CLOUDFLARE_API_TOKEN" \
CF_Email="admin@example.com" \
SYNO_USE_TEMP_ADMIN=1 \
/var/services/homes/sysadmin/acme.sh/acme_config/acme.sh \
  --home /var/services/homes/sysadmin/acme.sh/acme_config \
  --cert-home /var/services/homes/sysadmin/acme.sh/acme_certs \
  --config-home /var/services/homes/sysadmin/acme.sh/acme_config \
  --renew \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --deploy \
  --deploy-hook synology_dsm \
  -d '*.int.example.com'
```

In DSM: Control Panel > Task Scheduler > Create > Scheduled Task > User-defined script. The user has to be root or the DSM deploy fails. Daily at 3:00 AM is fine.

## How does SYNO_USE_TEMP_ADMIN work with the acme.sh Synology hook?

`SYNO_USE_TEMP_ADMIN=1`. The `synology_dsm` deploy hook cannot log in as a 2FA admin, so it creates a temporary admin, pushes the cert, and deletes that account. It exists for those few seconds of the deploy, not as a standing user.

Create a scoped Cloudflare API token, not a Global API Key. Permission is Zone > DNS > Edit, locked to the one zone. acme.sh writes the token into `acme_config/account.conf` and sets the file to 600. Back that directory up with the rest of the home share.

## Outcome

One wildcard Let's Encrypt cert covers the internal names on the Synology NAS. Port 80 stays closed. acme.sh renewals run at 3 AM, the `synology_dsm` hook lands them in DSM, and services pick up the new cert. The last time anyone clicked through DSM Certificate Manager on this NAS was the day we set this up.

Running IT in-house and want a second set of hands on jobs like this? That's [co-managed IT](/industries/co-managed-it): your person stays, and we cover after hours, overflow, and the tooling.

## Key takeaways

- DSM's Let's Encrypt is HTTP-01 only. A Synology wildcard certificate and names that are not on the public internet need a DNS challenge.
- Do not install acme.sh in /root on a Synology NAS. Put the acme.sh client in a home directory with certs and config split so it survives DSM updates and backups.
- Always issue acme.sh --staging first. Production is five certificates per registered domain per week.
- The synology_dsm deploy hook plus SYNO_USE_TEMP_ADMIN is how acme.sh deploys when the real admin account has 2FA. It creates a temp admin, pushes the cert, and deletes the account.
- Use a scoped Cloudflare token (Zone, DNS, Edit, one zone), not a Global API Key.
- One Task Scheduler job as root does acme.sh --renew and --deploy-hook synology_dsm to automate SSL renewal. Most mornings it is a no-op.
