Tested on a Dream Machine SE — your mileage may vary.
I used the acme.sh shell script to issue a certificate and set up automated renewal.
Start by SSHing into your Dream Machine SE.
Install acme.sh
in ~/.acme.sh
.
wget -O - https://get.acme.sh | sh -s [email protected]
Issue a certificate using a DNS challenge. I used Digital Ocean, but acme.sh
supports a lot of DNS APIs.
Check the list here: 👉 https://github.com/acmesh-official/acme.sh/wiki/dnsapi
~/.acme.sh/acme.sh --issue --dns dns_dgon -d example.com -d '*.example.com'
💡 The DNS challenge is only needed when initially issuing the certificate. Renewals don’t require it (at least for DigitalOcean).
Once the certificate has been issued, copy it to your local machine.
On your machine execute:
scp -r user@gateway_ip:/your_user/.acme.sh/your_domain_ecc ~/local_folder/
Go to the UniFi Network App (Settings > Control Plane > Console > Certificates), add the new certificate and activate it. The UI will display the expiration date of the certificate, which typically expires in 3 months.
The UniFi OS stores these certificates in the folder /data/unifi-core/config
. For each certificate they'll create 2 files.
UUID.cer
UUID.key
In that folder the settings.yaml
file contains a property activeCertId
which points to the active certificate.
Let's create symlinks to point these two UniFi OS-generated files to the Let's Encrypt-issued certificate. This step might not be strictly necessary, but we'll include it for completeness.
cd /data/unifi-core/config
# Make a backup of the original files. Replace UUID with the correct value!
mv [UUID].cer [UUID]_backup.cer
mv [UUID].key [UUID]_backup.key
# Create the symlinks
ln -s /your_user/.acme.sh/your.domain_ecc/your.domain.cer [UUID].cer
ln -s /your_user/.acme.sh/your.domain_ecc/your.domain.key [UUID].key
When you installed acme.sh
it also registered a crontab that runs daily to renew your certificate.
crontab -l
Output:
30 9 * * * "/your_user/.acme.sh"/acme.sh --cron --home "/your_user/.acme.sh" > /dev/null
That'll take care of renewing the certificate.
When you uploaded the certificate the UniFi OS also registered it in the Java keystore. It is this certificate that is served!
You can extract the cert from the keystore and inspect it using the keytool
CLI. The password is aircontrolenterprise
. This is built into the Unifi Controller, it's not a user-set password.
// Extract the certificate
keytool -exportcert -alias unifi -keystore /usr/lib/unifi/data/keystore -file ./my_cert.cer -rfc
// Inspect the certificate
openssl x509 -noout -text -in my_cert.cer
// Cleanup
rm -rf ./my_cert.cer
When the acme.sh
crontab job runs and renews the certificate it must also update it into the keystore.
To automatically deploy the renewed certificate, set up the appropriate deploy hook:
~/.acme.sh/acme.sh --deploy -d example.com --deploy-hook unifi
Reference: 👉 https://github.com/acmesh-official/acme.sh/wiki/deployhooks#23-deploy-the-cert-on-a-unifi-controller-or-cloud-key
The script mentions it works on the Dream Machine, but for me it also worked fine on the Dream Machine SE.
Next time the cron job runs, it will:
- Renew your certificate.
- Register it in the keystore.
- Restart the Unifi controller.
You'll notice that when the certificate is renewed, the expiration date in the Unifi Network app is not updated.
The UniFi OS runs a mongodb and postgres database.
- mongodb: port
27117
, no auth - postgres: port
5432
, user postgres, no password (hint: it's stored in here)
When you upload the certificate, the UniFi OS creates a record in the table user_certificates
in the unifi-core
postgres database. They extract the metadata from the certificate and snapshot it in this table. This does not get updated when the certificate is renewed. The ID of this record is a UUID, which corresponds to the certificate files in the /data/unifi-core/config
folder.
In the UniFi Network UI they'll keep displaying the metadata (expiration data...etc.) of the original certificate as it was when you uploaded it.
Let's just set this expiration date far into the future so we don't get any warnings in the UI.
Create an SSH tunnel to port forward port 5432
on your local machine to the same port on the UDM SE.
ssh -fNT -L 5432:127.0.0.1:5432 user@gateway_ip
Modify the valid_to
column and set it as far in the future as you want to.
This is what they've built a REST API on top. The UI fetches this resource.
GET https://your.domain/api/userCertificates
If you want the UniFI Network UI to display the correct expiration date, you can use the attached user_certificate.sh
script I created. It extracts metadata from the the certificate and updates the corresponding database entry in the user_certificates
table.
The script uses a package called jc (JSON Convert), to parse the certificate to JSON to extract the metadata. You'll need to install the package.
pip3 install jc
Remark: If you install it using apt
it might install an outdated package which cannot parse X.509 certificates.
Create a new script called user_certificate.sh
inside of a scripts
folder in your user's home folder.
mkdir scripts
cd scripts
touch user_certificate.sh
chmod +x ./user_certificate.sh
chmod 700 ./user_certificate.sh
Edit the file with vim and copy/paste the script. Be sure to update the CERT_PATH
and CERT_ID
configuration parameters as needed for your environment!
Setup a crontab.
crontab -e
Add the following line.
0 9 * * * /your_user/scripts/user_certificate.sh > /dev/null
This will run the script once per day. Adjust the schedule as desired.
- You can also setup notify hooks. For example, sending a WhatsApp message when the cert is renewed.
- This approach also works fine on a Synology DSM (DSM 7.2). The
acme.sh
script has a deploy hook for Synology as well. No need for any custom scripts. After deploying the certificate the DSM UI correctly displays the updated certificate.