Intended for UGREEN NASync NAS machines. This precise workflow was used on my own setup.
This guide is good for:
- Select Linux distros (Arch-based in particular)
- macOS
While reading this guide, keep in mind that mentions of example.com are placeholder; make sure to add your specific domain when copying commands.
- DNS: Cloudflare (DNS-only)
- Domain registrar: any
- Certificate authority: Letβs Encrypt
- ACME client: acme.sh (docker container)
- Validation method: DNS-01 (wildcard)
- Scope: LAN-only (no port forwarding, no wan)
- DNS-01 allows certificate issuance without exposing the NAS to the internet
- Cloudflare API tokens do not require IP whitelisting
- Wildcard cert (
*.example.com) supports future services - UGOS UI remains stable and vendor-supported (e.g., remote access)
Any will do!
First of all, why Cloudflare? For starters, as of this date, you get a few things:
- You can block crawlers (including AI!) from their dashboard.
- Registrars (e.g. Namecheap) can require an IP whitelist to use their APIs (which you could otherwise use use in place of Cloudflare).
- It's free. π
TL;DR using Cloudflare as a DNS is very useful. If you get these benefits already from your registrar or you just don't like Cloudflare, then omitting Cloudflare is perfectly reasonable. Just make sure to use the right DNS API with ACME (implemented later) or your Let's Encrypt certificate won't populate.
After you add your domain to your Cloudflare dashboard, they give you some nameservers. Add them to your domain, taking care to port any records you may need from your regsitrar. Cloudflare claims there should be little, if any, downtime, so keep in mind you could experience a hiccup.
If you connected to Cloudflare, check that the DNS is working:
$ dig +short NS example.comCreate and store an API token.
IMPORTANT: Limit access only to your specific domain
Then get and store your zone / account IDs.
NOTE: You can defer this step until right before you generate your Let's Encrypt certificate, to be safer.
First, get your NAS' IP address (it looks like 192.168.x.x; you use it to access UGOS). Create a new Cloudflare DNS record pointing to the IP, preferably on a subdomain such as nas or something unique.
Example:
- Type: A
- Name: nas
- Content: 192.168.x.x
- Proxy: disabled (important!)
- TTL: auto
Enable your shared folder if you haven't already. You'll be prompted if you haven't when you open the Files app for the first time.
Locate and download this docker image: neilpang/acme.sh
Create a new container for it, with these settings:
- Container name: example-acme
- Memory limit: 2000-4000 MB (you don't need much)
- Auto restart: [x]
- Environment (variables):
- CF_Token:
<API_TOKEN> - CF_Zone_ID:
<ZONE_ID> - CF_Account_ID:
<ACCOUNT_ID>
- CF_Token:
- Volumes:
- NAS directory/file:
<shared_folder>/docker/acme.sh - Container directory/file:
/acme.sh - Container permissions: Read/Write
- NAS directory/file:
- Network mode: bridge
- Commands:
daemon
NOTE: If you didn't use Cloudflare, make sure to use your registrar's corresponding API tokens.
Confirm and start the container.
Add a certs output folder within the container directory (on NAS). Both your certificate and key will end up here later for easier access.
/docker/acme.sh/
βββ certs/
βββ ...
Do yourself a lil favor and set up an ssh key to make it easier going forward. You can also skip this if you're fine with pasting your NAS user password every time you want to SSH.
SSH into the NAS:
$ ssh username@192.168.X.X -p PORT_HEREThen...
$ sudo -i
$ docker exec -it example-acme sh
$ acme.sh --set-default-ca --server letsencrypt
$ acme.sh --issue --dns dns_cf -d '*.example.com' --keylength ec-256
$ acme.sh --install-cert -d '*.example.com' --key-file /acme.sh/certs/example.key --fullchain-file /acme.sh/certs/example.crt --reloadcmd "echo cert updated $(date -u)"NOTE: If you didn't use Cloudflare, replace
dns_cfwith your corresponding DNS API!
The --reloadcmd flag is important here: this way your container continues to check for certificate renewals (usually daily), only updating if you're within 30 days of expiration.
Still from within your container, confirm file output:
$ ls -la /acme.sh/certs/Confirm reload cron is running:
$ acme.sh --list | grep exampleConfirm issuer:
$ openssl x509 -in /acme.sh/certs/example.crt -noout -issuer -subject -datesConfirm TLS handshake (using HTTPS port):
$ curl -vk https://nas.example.com:9443NOTE:
9443should be the default. You can confirm under Control Panel (app) -> Device Connection -> Portal Settings.
Feel free to leave the container environment and go back to local.
Try copying over your generated certificate/key files:
$ scp username@nas.example.com:/docker/acme.sh/certs/example.{key,crt} ~/DownloadsYour NAS' filesystem permissions may reject this command... if that happens, go back into the container and run this:
$ chmod 755 /docker/acme.sh/certs
$ chmod 644 /docker/acme.sh/certs/*.crt
$ chmod 600 /docker/acme.sh/certs/*.keyNOTE: Why these specific permission values? Keeps private key private while retaining
scpreadability to both the certificate and key.
Finally, moment of truth: upload the certificate and key under Control Panel -> Security -> Certificates.
- Private key -> *.key
- Certificate -> *.crt
- Intermediate certificate -> leave empty
Once your ACME instance is up and running, you'll want to confirm it's operating as expected. You can do this easily by viewing the container logs at Docker -> example-acme -> Log
Within 24 hours, you should see something like this:
[Wed Dec 24 18:03:00 UTC 2025] ===Starting cron===
[Wed Dec 24 18:03:02 UTC 2025] Already up to date!
[Wed Dec 24 18:03:02 UTC 2025] Upgrade successful!
[Wed Dec 24 18:03:02 UTC 2025] Automatically upgraded to: x.x.x
[Wed Dec 24 18:03:02 UTC 2025] Renewing: '*.example.com'
[Wed Dec 24 18:03:02 UTC 2025] Renewing using Le_API=https://acme-v02.api.letsencrypt.org/directory
[Wed Dec 24 18:03:02 UTC 2025] Skipping. Next renewal time is: 2026-02-18T22:29:56Z
[Wed Dec 24 18:03:02 UTC 2025] Add '--force' to force renewal.
[Wed Dec 24 18:03:02 UTC 2025] Skipped *.example.com_ecc
[Wed Dec 24 18:03:02 UTC 2025] ===End cron===
Note that it says Skipping. after attempting to renew. This is expected until an actual renewal occurs, at which point you can safely update your key/certificate in UGOS.
If:
- Rebuilding your
acme.shdocker container - Remounting
/docker/acme.sh -> /acme.sh - Changing Cloudflare token
Then, in your acme.sh container...
$ acme.sh --list
$ acme.sh --renew -d '*.example.com'Common renewal failure causes:
- Expired / revoked Cloudflare api token
CF_Tokenmissing zone DNSEditpermission- Docker container stopped running (is auto restart enabled?)
- NAS clock drift (NTP disabled)
- Don't forward any default ports (
80/443) - Don't enable UGOS remote access
- Don't proxy
nas.*records through Cloudflare
This process assumes you've enabled a personal folder in UGOS.
$ ssh-keygen -t ed25519Create a config or add to an existing one (~/.ssh/config):
Host nas.example.com
HostName nas.example.com
User username
IdentityFile ~/.ssh/id_ed25519
Output and copy your public key:
$ cat ~/.ssh/id_ed25519.pubSSH into the NAS, then...
$ mkdir ~/.ssh
$ touch ~/.ssh/authorized_keys
$ echo "PUBLIC_KEY" > ~/.ssh/authorized_keysThen try an ssh into the NAS again. If it fails or you get a password prompt, you may need to do this from inside the NAS:
$ chmod 700 ~
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keysYou can always set up an email/notification for this, but I keep a calendar event every 60 days or so to check & renew instead.
First, enable an SSH tunnel from UGOS.
Copy, create locally, and add your details to this script. When it's time to check, run it for quick confirmation. It assumes you've set up SSH keys but should work without too.