- Published on
Unifi Network Server Certificates
- Authors

- Name
- Anthony Bond
Ubiquiti Network hardware can be known as a prosumer network system for folks looking to dive into the networking space. What started as a single access point and security gateway, has evolved into a small office network that handles realtime streaming of camera feeds and providing in-depth network analytics.
Overtime I've found myself burning countless hours tweaking the network, vlans, firewalls, and causing network disruption to my family's access to the internet. Ubiquiti develops a suite of software tools to streamline management of their devices and provide users with enterprise grade networking features. One of those tools is the UniFi Network Server.
While Ubiquiti provides cloud hosting and integrated Cloud Gateways that include the UniFi Network Server. Users can elect to run the service on their own hardware. I've been using an old MacPro running Ubuntu with Docker to host my network server for years. Now that I've stopped playing with all the network configurations, management is pretty straightforward. I update the container image to a new release version and restart the service. UniFi automatically runs backups once a day and those are saved to the host filesystem. It's a pretty sweet setup that is no fuss and online 99% of the time.
One challenge with this setup is the self-signed certificate that UniFi presents to the web server. Browsers do not like it when you hit a self-signed certificate, nor do other applications that interact with the UniFi network server. For me, this drove me crazy always hitting the untrusted certificate error. I had to do something about it!
I went to the web in search of solution and what do you know the UniFi Network Server provides an import_key_cert command. Problem is this would sometimes work and sometimes throw and obscure error. The community forms is riddled with comments and workarounds, but nothing worked for what I wanted.
Remember how I said Ubiquiti is known as prosumer product. Well most of those community members didn't have a problem spending money for a certificate to be issued through DigiCert or Namecheap. Me, I like the free community route of Let's Encrypt.
So, how do I go about using a Let's Encrypt certificate on the UniFi Network Server? For a while cert-manager was the tool of choice generating certificates every 60 days and uploading to my container. While this was great, I eventually lost my Raspberry Pi Kubernetes cluster to a series of hardware failures and frustration of playing with Kubernetes.
I finally sat down over the weekend (two years later) and revisited my setup. I'm happy to say, I've solved my problem with a minimal configuration using Lego, bash, and systemd timers. Below is the path I took to solve my certificate challenges, hopefully it can help you solve your certificate problems.
Prerequisites:
- DNS service provider (Cloudflare)
- Domain name
- Lego install
- Linux
Step 1: Lego Hook
The lego CLI allows users to invoke hooks. These hooks can be programs or bash scripts. I use a hook to copy the certificates to a directory mounted by the UniFi container. After copying the certificates the script executes a docker command to signal UniFi to load the new certificates and restart the container.
Feel free to copy the contents below to /root/legohook.sh or another location to fit your needs.
Make you update the CERT_NAME to your domain name and the docker command if you're not using a docker container.
#!/usr/bin/env bash
#
# Load the Cloudflare token in your environment
# CLOUDFLARE_DNS_API_TOKEN="somevalue"
#
# Create certificate
# lego -d unifi.example.com --email="[email protected]" --dns="cloudflare" --dns.resolvers 8.8.8.8:53 run --run-hook="/root/legohook.sh"
#
# Renew certificate
# lego --email="[email protected]" --domains="unifi.example.com" --dns="cloudflare" --dns.resolvers 8.8.8.8:53 renew --renew-hook="/root/legohook.sh"
# Hook provided env vars # LEGO_ACCOUNT_EMAIL: the email of the account.
# LEGO_CERT_DOMAIN: the main domain of the certificate.
# LEGO_CERT_PATH: the path of the certificate.
# LEGO_CERT_KEY_PATH: the path of the certificate key.
set -xe
CERT_NAME=unifi.example.com
LEGO_PATH=/root/.lego
CERTS_PATH=$LEGO_PATH/certificates
UNIFI_DIR=/data/unifi
CONTAINER_ID=$(docker ps -f name=unifi -q)
# Copy certificates to Unifi Data directory on the host
cp ${CERTS_PATH}/${CERT_NAME}.key ${UNIFI_DIR}/${CERT_NAME}.key
cp ${CERTS_PATH}/${CERT_NAME}.crt ${UNIFI_DIR}/${CERT_NAME}.crt
cp ${CERTS_PATH}/${CERT_NAME}.issuer.crt ${UNIFI_DIR}/${CERT_NAME}.issuer.crt
# Unifi import certificates
docker exec $CONTAINER_ID java -jar /usr/lib/unifi/lib/ace.jar import_key_cert ${CERT_NAME}.key ${CERT_NAME}.crt ${CERT_NAME}.issuer.crt
# Restart Unifi
systemctl restart unifi
Step 2: Create the first certificate
Use the following lego command to create the initial certificate. Replace the parameters to meet your requirements.
lego -d unifi.example.com \
--email="[email protected]" \
--dns="cloudflare" \
--dns.resolvers 8.8.8.8:53 \
run \
--run-hook="/root/legohook.sh"
Note, you can also use the Let's Encrypt staging api to test the entire process. Add the following parameter before the run flag to receive certificates signed by the staging Let's Encrypt server.
--server=https://acme-staging-v02.api.letsencrypt.org/directory
Step 3: Configure the timer
Setup a systemd timer to execute once a day to invoke the Lego renew command. Don't worry lego will exit quickly when the certificate is not ready for renewal.
Timer
Create a timer file under /etc/systemd/system/lego.timer with the following contents.
[Unit]
Description="Run lego.service 45 minutes after boot and every 24 hours relative to activation time"
[Timer]
OnBootSec=45min
OnUnitActiveSec=24h
OnCalendar=Mon..Sun *-*-* 10:01:00
Unit=lego.service
[Install]
WantedBy=multi-user.target
Unit
Create a systemd unit for the timer to invoke. Place the following contents in /etc/systemd/system/lego.service and update to meet your needs.
You can see the unit invokes the lego command for renew and calls the legohook.sh script we created in Step 1. I also pull in environment variables from the /root/.cloudflare file to allow lego access to Cloudflare.
[Unit]
Description="Unifi certificate renewal with lego"
[Service]
Type=oneshot
WorkingDirectory=/root
EnvironmentFile=/root/.cloudflare
ExecStart=/usr/local/bin/lego --email="[email protected]" --domains="unifi.example.com" --dns="cloudflare" --dns.resolvers 8.8.8.8:53 renew --renew-hook="/root/legohook.sh"
Step 4: Reload Systemd
Reload systemd and test your timer.
systemctl daemon-reload && systemctl start lego.timer
Validate the command ran successfully, you should have a new certificate loaded into your UniFi Network server and logs within the journal stating your certificate is not ready for renewal.
Example successful logs: journalctl -u lego.service
Aug 18 20:38:55 host01 systemd[1]: Started "Unifi certificate renewal with lego".
Aug 18 20:38:56 host01 lego[209811]: 2024/08/18 20:38:56 [unifi.example.com] The certificate expires in 89 days, the number of days defined to perform the renewal is 30: no renewal.
Aug 18 20:38:56 host01 systemd[1]: lego.service: Deactivated successfully.
Don't forget to enable the timer, so systemd can execute the command daily.
systemctl enable lego.timer
Feel free to reach out, if you run into any challenges or have questions. Thanks for making it this far.