I wanted to deploy the certificate to my Unifi UCG Fiber (Unifi OS 5.0.16) remotely. I did not want to install the deploy hook locally and tried emulating it via SSH (systemctl restart unifi-core, systemctl restart nginx, unifi-os restart`) My certificate would always revert after about 10-15 minutes.
I inspected the communication from the web console and came up with the following script that cleanly applies the certificate using that method. Then whatever internal magic they do is performed as necessary and the cert sticks.
I am not sure how or if this can be adopted into acme.sh, but perhaps others find this useful (I saw reports about certs reverting like I was experiencing.)
#!/bin/sh
# Deploy cert to UniFi OS via API
# Usage: deploy-unifi.sh <host> <username> <password> <cert_file> <key_file>
UNIFI_HOST="$1"
UNIFI_USER="$2"
UNIFI_PASS="$3"
CERT_FILE="$4"
KEY_FILE="$5"
COOKIE_JAR=$(mktemp)
# Login and get TOKEN cookie
curl -sk -c "$COOKIE_JAR" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$UNIFI_USER\",\"password\":\"$UNIFI_PASS\",\"rememberMe\":false}" \
"https://$UNIFI_HOST/api/auth/login" > /dev/null
# Extract TOKEN from cookie jar
TOKEN=$(grep -m1 'TOKEN' "$COOKIE_JAR" | awk '{print $7}')
if [ -z "$TOKEN" ]; then
echo "[ERROR] login failed — no TOKEN cookie received"
rm -f "$COOKIE_JAR"
exit 1
fi
# Extract CSRF token from JWT payload (base64url decode middle section)
PAYLOAD=$(echo "$TOKEN" | cut -d'.' -f2)
PAD=$(( ${#PAYLOAD} % 4 ))
[ "$PAD" -ne 0 ] && PAYLOAD="${PAYLOAD}$(printf '%0.s=' $(seq 1 $((4 - PAD))))"
CSRF=$(echo "$PAYLOAD" | tr '_-' '/+' | base64 -d 2>/dev/null | grep -o '"csrfToken":"[^"]*"' | cut -d'"' -f4)
if [ -z "$CSRF" ]; then
echo "[ERROR] could not extract CSRF token from JWT"
rm -f "$COOKIE_JAR"
exit 1
fi
# Escape cert and key newlines for JSON
CERT=$(sed ':a;N;$!ba;s/\n/\\n/g' "$CERT_FILE")
KEY=$(sed ':a;N;$!ba;s/\n/\\n/g' "$KEY_FILE")
# Upload certificate
UPLOAD_RESPONSE=$(curl -sk \
-b "$COOKIE_JAR" \
-H "Content-Type: application/json" \
-H "X-Csrf-Token: $CSRF" \
-d "{\"name\":\"$UNIFI_HOST\",\"cert\":\"$CERT\",\"key\":\"$KEY\"}" \
"https://$UNIFI_HOST/api/userCertificates")
# Extract cert ID from response
CERT_ID=$(echo "$UPLOAD_RESPONSE" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$CERT_ID" ]; then
# Check if it's a duplicate — cert already exists, look it up by name
if echo "$UPLOAD_RESPONSE" | grep -q 'USER_CERTIFICATE_DUPLICATE'; then
echo "[INFO] certificate already exists, looking up by name..."
LIST_RESPONSE=$(curl -sk \
-b "$COOKIE_JAR" \
-H "X-Csrf-Token: $CSRF" \
"https://$UNIFI_HOST/api/userCertificates")
CERT_ID=$(echo "$LIST_RESPONSE" | sed 's/},{/}\n{/g' | grep '"name":"'"$UNIFI_HOST"'"' | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
fi
fi
if [ -z "$CERT_ID" ]; then
echo "[ERROR] upload failed or no ID returned: $UPLOAD_RESPONSE"
rm -f "$COOKIE_JAR"
exit 1
fi
echo "[OK] certificate ready (id: $CERT_ID)"
# Activate the certificate
ACTIVATE_RESPONSE=$(curl -sk -w "\n%{http_code}" \
-X PUT \
-b "$COOKIE_JAR" \
-H "Content-Type: application/json" \
-H "X-Csrf-Token: $CSRF" \
-d '{"active":true}' \
"https://$UNIFI_HOST/api/userCertificates/$CERT_ID/status")
HTTP_CODE=$(echo "$ACTIVATE_RESPONSE" | tail -n1)
BODY=$(echo "$ACTIVATE_RESPONSE" | head -n-1)
rm -f "$COOKIE_JAR"
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "[OK] certificate activated on $UNIFI_HOST (HTTP $HTTP_CODE)"
else
echo "[ERROR] activation failed (HTTP $HTTP_CODE): $BODY"
exit 1
fi
I wanted to deploy the certificate to my Unifi UCG Fiber (Unifi OS 5.0.16) remotely. I did not want to install the deploy hook locally and tried emulating it via SSH (
systemctl restart unifi-core,systemctl restart nginx,unifi-os restart`) My certificate would always revert after about 10-15 minutes.I inspected the communication from the web console and came up with the following script that cleanly applies the certificate using that method. Then whatever internal magic they do is performed as necessary and the cert sticks.
I am not sure how or if this can be adopted into acme.sh, but perhaps others find this useful (I saw reports about certs reverting like I was experiencing.)