UniFi Controller 5.11, Let’s Encrypt SSL and Docker

A slight change of plans from earlier posts on the topic of UniFi Controllers! Here’s how to get a UniFi Controller running inside a Docker container, along with a trusted Let’s Encrypt SSL certificate.

Note: this guide assumes you’re configuring things on a server or VM with public Internet access. You’ll also need a fixed public IP and functional DNS to get an SSL certificate.

Here we go:

Firewall

UniFi needs a bunch of inbound ports open. Here’s the official list – it differs slightly to what I use:

PortDescription
UDP/3478STUN – required for device communication with the controller
TCP/8080Inform – required to adopt devices
TCP/8443GUI – required even if you use the Cloud Controller access
TCP/8880Captive Portal – HTTP – only needed if you use the captive portal feature
TCP/8843Captive Portal – HTTPS – only needed if you use the captive portal feature
TCP/6789Speed Test – only needed if you use the speed test feature

Let’s Encrypt also needs a port open:

PortDescription
TCP/80HTTP – required for the HTTP-01 challenge type

I use ufw to configure iptables – first, set up an application definition for the UniFi Controller – in /etc/ufw/applications.d/unifi:

[unifi]
title=unifi
description=UniFi Controller
ports=6789,8080,8880,8443,8843/tcp|3478/udp

Run the following four commands to configure and enable the firewall. I’ve made some assumptions about what’s needed – you may need to customise things a little more:

sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow unifi
sudo ufw enable

User Account

UniFi probably shouldn’t be run as root – this is generally a good idea, plus it may also become a requirement for the Docker image I’m using in the future. This will also affect what ports you can configure the controller to use – the default ports work fine for any user, but changing any of the ports to <1024 requires root.

Create the unifi user and group accounts:

sudo adduser unifi --system --group --no-create-home

Pay attention to the UID and GID that get created; you need them in the Docker Compose file below.

Docker

Here’s the tl;dr version of the installation instructions, but if you want to read the full version with all the details – check the Docker website.

Configure the Docker repository – it contains a more up-to-date version:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"

Install Docker and related tools:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

UniFi Controller

There are a number of UniFi Docker images out there, but I like the one by jacobalberty as it’s kept up to date – plus it exposes a volume for adding trusted certificates. His Docker Compose file isn’t quite to my taste, so I’ve adjusted things. Create the file /opt/unifi/docker-compose.yml:

version: '2.2'
services:
  mongo:
    image: 'mongo:3.4'
    restart: always
    volumes:
      - db:/data/db
  controller:
    image: 'jacobalberty/unifi:${TAG:-latest}'
    depends_on:
      - mongo
    init: true
    restart: always
    volumes:
      - data:/unifi/data
      - log:/unifi/log
      - cert:/unifi/cert
      - init:/unifi/init.d
    environment:
      RUNAS_UID0: 'false'
      UNIFI_UID: 100
      UNIFI_GID: 100
      JVM_MAX_THREAD_STACK_SIZE: 1280k
      DB_URI: mongodb://mongo/unifi
      STATDB_URI: mongodb://mongo/unifi_stat
      DB_NAME: unifi
    ports:
      - '3478:3478/udp'
      - '6789:6789/tcp'
      - '8080:8080/tcp'
      - '8443:8443/tcp'
      - '8880:8880/tcp'
      - '8843:8843/tcp'
  logs:
    image: bash
    depends_on:
      - controller
    command: bash -c 'tail -F /unifi/log/*.log'
    restart: always
    volumes:
      - log:/unifi/log

volumes:
  db:
  data:
  log:
  cert:
  init:

Note: if you’re going to change the location of this file, it should be in a directory called ‘unifi’. Bring the stack up like so (it will take a fair while first time around):

sudo docker-compose up -d

Install SSL

This part requires a few sections that need to be completed in order – first you need a script to load the SSL certificate into the UniFi Docker cert volume, then you need to run a certbot command to obtain the certificate.

If you use a provider other than Let’s Encrypt for SSL certificates, these instructions will need to be adjusted.

UniFi SSL Deploy Script

It may seem backwards, but the deploy script needs to exist before obtaining the certificate. Read through this script carefully and adjust any domains and directories as needed. Create the file /opt/unifi/unifi-ssl-deploy.sh:

#!/bin/sh

set -e

for domain in $RENEWED_DOMAINS; do
  case $domain in
  unifi.example.com)
    # Where does the Docker cert data volume live?
    cert_root=/var/lib/docker/volumes/unifi_cert/_data
    # Where is the Docker Compose file?
    compose_file=/opt/unifi/docker-compose.yml

    # Make sure the certificate and private key files are
    # never world readable, even just for an instant while
    # we're copying them into cert_root.
    umask 077

    cp "$RENEWED_LINEAGE/cert.pem" "$cert_root/cert.pem"
    cp "$RENEWED_LINEAGE/privkey.pem" "$cert_root/privkey.pem"
    cp "$RENEWED_LINEAGE/chain.pem" "$cert_root/chain.pem"

    # Apply the proper file permissions
    # Files can be owned by root
    chmod 400 "$cert_root/cert.pem" \
      "$cert_root/privkey.pem" \
      "$cert_root/chain.pem"

    # Restart the Docker container
    docker-compose -p unifi -f $compose_file stop
    docker-compose -p unifi -f $compose_file start
    ;;
  esac
done

Now make the file executable:

sudo chmod a+x unifi-ssl-deploy.sh

Obtain SSL with Certbot

Conveniently, Certbot has its own mechanism for obtaining an SSL certificate without using a webserver. If you have a webserver configured, you will want to adjust these instructions accordingly.

As above, adjust the following to suit your domain:

sudo apt-get install certbot
sudo certbot certonly --standalone --domain unifi.example.com --deploy-hook /opt/unifi/unifi-ssl-deploy.sh

The command to obtain the certificate will ask a few questions – you may also see an error from the deploy script, but it’s not actually an error per se.

Note: After the deploy script has run, you need to wait up to 5 minutes for the UniFi Controller to fully start back up again. If you don’t, you’re likely to get an SSL error (PR_END_OF_FILE_ERROR) in the browser!

We’re all done – your UniFi Controller should now be available via: https://unifi.example.com:8443

Reverse Proxy

I’ve opted to not configure a reverse proxy, as I don’t believe one is needed. If port 8443 is blocked on your network, you can configure cloud access via https://unifi.ui.com.

If you want to configure a reverse proxy, note you’ll need something that handles websockets gracefully – Nginx and Traefik are probably your best options.

Configure Homebridge as a Service on Debian

Homebridge is a fantastic mechanism for getting non-HomeKit-certified smart home tech talking to Apple. It’s nerd tech written for nerds, so it’s not the easiest thing in the world to get running. Here are some instructions to make things work on Debian and friends (including Raspberry Pi and Ubuntu):

Note that these instructions are going off old memory, so they may be slightly wrong – I’ll retest and fix anything that needs fixing soon!

First thing’s first – make sure everything is up to date:

sudo apt-get update
sudo apt-get upgrade

Install Node.js

Next, install Node.js from the NodeSource GitHub (don’t use the repositories of your distro, as they’re probably outdated). After installation, update to the latest release and install build tools:

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

sudo npm cache clean -f
sudo npm install -g n
sudo n stable

sudo apt-get install -y build-essential

I like to reboot at this point, but that’s in no way essential.

Install Homebridge

The following installs Homebridge (note the --unsafe-perm flag) and the Homebridge Dummy Switches plugin. The plugin isn’t critical, but I seem to remember having difficulties getting things working without at least one plugin. Besides, the Dummy Switches plugin can be fairly handy for certain types of automation..:

sudo apt-get install libavahi-compat-libdnssd-dev
sudo npm install -g --unsafe-perm homebridge
sudo npm install -g homebridge-dummy

Next, create a very basic config at ~/.homebridge/config.json:

{
  "bridge": {
    "name": "Homebridge",
    "username": "CC:22:3D:E3:CE:30",
    "port": 51826,
    "pin": "031-45-154"
  },
  "description": "Homebridge Server",
  "platforms": [
    {}
  ],
  "accessories": [
    {
      "accessory": "DummySwitch",
      "name": "My First Switch"
    }
  ]
}

Run Homebridge directly at the command line to test:

homebridge

An ascii-art QR code will appear; you can use this code to add Homebridge to HomeKit using the Home app. Ignore warnings about the accessory not being certified.

Create systemd service for Homebridge

The instructions below assume you used the instructions above to install Node.js and Homebridge – paths may be different if you didn’t.

Pre-requisites

The following commands create the service user and directories, then moves the existing configuration into the appropriate locations for starting Homebridge as a service. Finally, set the relevant permissions..:

sudo useradd -M --system homebridge
sudo mkdir /var/lib/homebridge

sudo cp ~/.homebridge/config.json /var/lib/homebridge/
sudo cp -r ~/.homebridge/persist /var/lib/homebridge

sudo chmod -R 0777 /var/lib/homebridge

Systemd config files

Create /etc/default/homebridge:

# Defaults / Configuration options for homebridge
# The following settings tells homebridge where to find
# the config.json file and where to persist the data
# (i.e. pairing and others)
HOMEBRIDGE_OPTS=-U /var/lib/homebridge

# If you uncomment the following line, homebridge will log more
# You can display this via systemd's journalctl:
# journalctl -f -u homebridge
#DEBUG=*

Create /etc/systemd/system/homebridge.service:

[Unit]
Description=Node.js HomeKit Server
After=syslog.target network-online.target

[Service]
Type=simple
User=homebridge
EnvironmentFile=/etc/default/homebridge
ExecStart=/usr/local/bin/homebridge $HOMEBRIDGE_OPTS
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Finally, enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable homebridge
sudo systemctl start homebridge

Links and Howtos

Here’s what I used to get myself up and running:
https://github.com/nfarina/homebridge
https://github.com/nfarina/homebridge/blob/master/config-sample.json
https://gist.github.com/johannrichard/0ad0de1feb6adb9eb61a/
https://timleland.com/setup-homebridge-to-start-on-bootup/

UniFi Controller 5.8 on Debian 9

 

VPS services in Australia have become cheap enough for me to have a dedicated VM to run a UniFi controller, which changes how I’ve gone about setting it all up as compared with a past effort.

The UniFi controller is pretty memory hungry; I’ve got my VM configured with 1.5 GB of RAM and wouldn’t recommend anything less than that. 2 GB of RAM may be better on busier controllers. Aside from RAM, the system requirements are fairly minimal.

This recipe includes setting up the pre-requisites, installing UniFi, adding a firewall and an automatically-updating SSL certificate from Let’s Encrypt.

Pre-requisites

It’s a brand new VM, so make sure everything is up to date – plus install a few dependencies. I’ve had problems with package dependency conflicts when installing the UniFi .deb file, so I prefer to get a few things installed ahead of time.

Note that the versions of the Linux kernel and MongoDB are important! If you’re using this guide with versions of Linux other than Debian 9 – the latest version of Ubuntu has an incompatible version of MongoDB and earlier versions of Debian need a patched kernel. Caveat emptor.. 😉

sudo apt-get update && sudo apt-get upgrade && sudo apt-get dist-upgrade
sudo apt-get install mongodb openjdk-8-jre-headless jsvc ufw curl

Install the UniFi controller

As of now, version 5.8 of the UniFi controller is limited to beta testers – but joining the beta program is easy/quick/painless.

The latest 5.8 controller is available in the UniFi Beta Blog – download the Debian/Ubuntu .deb file and run the following:

sudo dpkg -i unifi_sysvinit_all.deb

If you’re upgrading from an earlier release, expect things to take a little while to stabilise post-install.

Configure the ufw firewall

If you’ve never configured ufw before, please see this DigitalOcean guide – it’s pretty well written and covers how to open up SSH. For obvious reasons, don’t turn on the firewall if you haven’t confirmed that you’ll still have access afterwards!

Create /etc/ufw/applications.d/unifi:

[UniFi]
title=UniFi Controller
description=UniFi Controller
ports=8080,8443,8880,8843/tcp|3478/udp

Enable the UniFi rules and confirm they’re working:

sudo ufw allow unifi
sudo ufw status verbose

Add Let’s Encrypt SSL

By default, the UniFi controller includes an untrusted certificate – which is almost useless, given how much the browsers complain about such certificates. While you can definitely get a certificate from a commercial CA, getting a certificate from Let’s Encrypt is fairly painless (and free!)

The first time you run the certbot command, you’ll need to agree to the Terms of Service and provide an email address – this is a first-time-only thing. You’ll also need to update the DNS name in the second command:

sudo apt-get install certbot
sudo certbot certonly --standalone --preferred-challenges http --pre-hook "ufw allow http" --post-hook "ufw deny http" -d your.dns.name.here

Once you’ve got the certificate, run a script to replace the certificate that comes with the UniFi controller, then add the script to cron to ensure the certificate is renewed and installed into the UniFi controller automagically.

Create /usr/local/bin/unifi-ssl-import.sh:

#!/usr/bin/env bash

# unifi_ssl_import.sh
# UniFi Controller SSL Certificate Import Script for Unix/Linux Systems
# by Steve Jenkins 
# Part of https://github.com/stevejenkins/ubnt-linux-utils/
# Incorporates ideas from https://source.sosdg.org/brielle/lets-encrypt-scripts
# Version 2.8
# Last Updated Jan 13, 2017

# Minor adjustments by Zac Ariel, called out with {ZA}
# Updated Sep 28, 2017

# REQUIREMENTS
# 1) Assumes you have a UniFi Controller installed and running on your system.
# 2) Assumes you already have a valid 2048-bit private key, signed certificate, and certificate authority
#    chain file. The Controller UI will not work with a 4096-bit certificate. See http://wp.me/p1iGgP-2wU
#    for detailed instructions on how to generate those files and use them with this script.

# KEYSTORE BACKUP
# Even though this script attempts to be clever and careful in how it backs up your existing keystore,
# it's never a bad idea to manually back up your keystore (located at $UNIFI_DIR/data/keystore on RedHat
# systems or /$UNIFI_DIR/keystore on Debian/Ubuntu systems) to a separate directory before running this
# script. If anything goes wrong, you can restore from your backup, restart the UniFi Controller service,
# and be back online immediately.

# CONFIGURATION OPTIONS
#UNIFI_HOSTNAME=hostname.example.com
UNIFI_HOSTNAME=your.dns.name.here
UNIFI_SERVICE=unifi

# Uncomment following three lines for Fedora/RedHat/CentOS
#UNIFI_DIR=/opt/UniFi
#JAVA_DIR=${UNIFI_DIR}
#KEYSTORE=${UNIFI_DIR}/data/keystore

# Uncomment following three lines for Debian/Ubuntu
UNIFI_DIR=/var/lib/unifi
JAVA_DIR=/usr/lib/unifi
KEYSTORE=${UNIFI_DIR}/keystore

# FOR LET'S ENCRYPT SSL CERTIFICATES ONLY
# Generate your Let's Encrtypt key & cert with certbot before running this script
#LE_MODE=no
LE_MODE=yes
LE_LIVE_DIR=/etc/letsencrypt/live

# THE FOLLOWING OPTIONS NOT REQUIRED IF LE_MODE IS ENABLED
#PRIV_KEY=/etc/ssl/private/hostname.example.com.key
#SIGNED_CRT=/etc/ssl/certs/hostname.example.com.crt
#CHAIN_FILE=/etc/ssl/certs/startssl-chain.crt

# CONFIGURATION OPTIONS YOU PROBABLY SHOULDN'T CHANGE
ALIAS=unifi
PASSWORD=aircontrolenterprise

#### SHOULDN'T HAVE TO TOUCH ANYTHING PAST THIS POINT ####

printf "\nStarting UniFi Controller SSL Import...\n"

# Check to see whether Let's Encrypt Mode (LE_MODE) is enabled

if [[ ${LE_MODE} == "YES" || ${LE_MODE} == "yes" || ${LE_MODE} == "Y" || ${LE_MODE} == "y" || ${LE_MODE} == "TRUE" || ${LE_MODE} == "true" || ${LE_MODE} == "ENABLED" || ${LE_MODE} == "enabled" || ${LE_MODE} == 1 ]] ; then
        LE_MODE=true
        printf "\nRunning in Let's Encrypt Mode...\n"
        PRIV_KEY=${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/privkey.pem
        SIGNED_CRT=${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/cert.pem
        CHAIN_FILE=${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/chain.pem
else
        LE_MODE=false
        printf "\nRunning in Standard Mode...\n"
fi

if [ ${LE_MODE} == "true" ]; then
        # Check to see whether LE certificate has changed
        printf "\nInspecting current SSL certificate...\n"
        if md5sum -c ${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/cert.pem.md5 &>/dev/null; then
                # MD5 remains unchanged, exit the script
                printf "\nCertificate is unchanged, no update is necessary.\n"
                exit 0
        else
        # MD5 is different, so it's time to get busy!
        printf "\nUpdated SSL certificate available. Proceeding with import...\n"
        fi
fi

# Verify required files exist
if [ ! -f ${PRIV_KEY} ] || [ ! -f ${SIGNED_CRT} ] || [ ! -f ${CHAIN_FILE} ]; then
        printf "\nMissing one or more required files. Check your settings.\n"
        exit 1
else
        # Everything looks OK to proceed
        printf "\nImporting the following files:\n"
        printf "Private Key: %s\n" "$PRIV_KEY"
        printf "Signed Certificate: %s\n" "$SIGNED_CRT"
        printf "CA File: %s\n" "$CHAIN_FILE"
fi

# Create temp files
P12_TEMP=$(mktemp)
CA_TEMP=$(mktemp)

# Stop the UniFi Controller
printf "\nStopping UniFi Controller...\n"
# {ZA} update to a systemd style command
#service ${UNIFI_SERVICE} stop
systemctl stop ${UNIFI_SERVICE}

if [ ${LE_MODE} == "true" ]; then
        
        # Write a new MD5 checksum based on the updated certificate     
        printf "\nUpdating certificate MD5 checksum...\n"

        md5sum ${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/cert.pem > ${LE_LIVE_DIR}/${UNIFI_HOSTNAME}/cert.pem.md5 
        
        # Create local copy of cross-signed CA File (required for keystore import)
        # Verify original @ https://www.identrust.com/certificates/trustid/root-download-x3.html
  cat > "${CA_TEMP}" <<'_EOF'
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
_EOF
fi

# Create double-safe keystore backup
if [ -s "${KEYSTORE}.orig" ]; then
        printf "\nBackup of original keystore exists!\n"
        printf "\nCreating non-destructive backup as keystore.bak...\n"
        cp ${KEYSTORE} ${KEYSTORE}.bak
else
        cp ${KEYSTORE} ${KEYSTORE}.orig
        printf "\nNo original keystore backup found.\n"
        printf "\nCreating backup as keystore.orig...\n"
fi
         
# Export your existing SSL key, cert, and CA data to a PKCS12 file
printf "\nExporting SSL certificate and key data into temporary PKCS12 file...\n"

openssl pkcs12 -export \
-in ${SIGNED_CRT} \
-inkey ${PRIV_KEY} \
-CAfile ${CHAIN_FILE} \
-out ${P12_TEMP} -passout pass:${PASSWORD} \
-caname root -name ${ALIAS}
        
# Delete the previous certificate data from keystore to avoid "already exists" message
printf "\nRemoving previous certificate data from UniFi keystore...\n"
keytool -delete -alias ${ALIAS} -keystore ${KEYSTORE} -deststorepass ${PASSWORD}
        
# Import the temp PKCS12 file into the UniFi keystore
printf "\nImporting SSL certificate into UniFi keystore...\n"
keytool -importkeystore \
-srckeystore ${P12_TEMP} -srcstoretype PKCS12 \
-srcstorepass ${PASSWORD} \
-destkeystore ${KEYSTORE} \
-deststorepass ${PASSWORD} \
-destkeypass ${PASSWORD} \
-alias ${ALIAS} -trustcacerts

# Import the certificate authority data into the UniFi keystore
printf "\nImporting certificate authority into UniFi keystore...\n\n"
if [ ${LE_MODE} == "true" ]; then
        # Import with additional cross-signed CA file
        java -jar ${JAVA_DIR}/lib/ace.jar import_cert \
        ${SIGNED_CRT} \
        ${CHAIN_FILE} \
        ${CA_TEMP}
else
        # Import in standard mode
        java -jar ${JAVA_DIR}/lib/ace.jar import_cert \
        ${SIGNED_CRT} \
        ${CHAIN_FILE}
fi      

# Clean up temp files
printf "\nRemoving temporary files...\n"
rm -f ${P12_TEMP}
rm -f ${CA_TEMP}
        
# Restart the UniFi Controller to pick up the updated keystore
printf "\nRestarting UniFi Controller to apply new Let's Encrypt SSL certificate...\n"
# {ZA} update to a systemd style command
#service ${UNIFI_SERVICE} start
systemctl start ${UNIFI_SERVICE}

# That's all, folks!
printf "\nDone!\n"

exit 0

Make the file executable and then run it to ensure it actually replaces the certificate in the UniFi controller – note that this script may take some time to complete:

sudo chmod 755 /usr/local/bin/unifi-ssl-import.sh
sudo /usr/local/bin/unifi-ssl-import.sh

Once the import process is working properly, automate it with a daily cron job.

Create /etc/cron.daily/unifi-ssl-import:

#!/bin/sh
/usr/local/bin/unifi-ssl-import.sh

Finally, make the cron script executable:

sudo chmod 755 /etc/cron.daily/unifi-ssl-import