Network, UniFi

Run the ISN UniFi connector as a service

The ISN connector is a small program that sits next to your UniFi controller and talks to ISN for you, so nothing on your firewall needs opening. This guide shows how to keep it running for good: it starts by itself after every reboot and starts again if it ever stops. It covers what the computer needs, the UniFi account the connector uses, ready-made scripts for Linux, Windows and Docker, and how to prove it survives a reboot.

1

What the connector is, in plain words

Your UniFi controller (a Cloud Key, a Dream Machine or a computer running the UniFi Network application) lives inside your own network. ISN cannot reach into it, and you should not open it to the internet. The connector solves this: it runs on a small always-on computer inside your network, calls out to ISN over the normal secure web port, asks "is there anything to do?", and does it on the controller (let a guest in, cut one off, restart an access point). The controller login is typed only on that computer. It is never sent to ISN.

Because it is an ordinary program, you start it with a command. For production you want it to start by itself after a power cut or reboot and to restart if it crashes. That is what the scripts on this page set up. You first create a connector in your dashboard (Network, Connect, UniFi) to get the program, then use this page to make it permanent.

2

What the computer needs

ItemRequirement
Operating systemAnything that runs Node.js 18 or newer: Windows 10 or 11, Windows Server, Linux (Debian, Ubuntu, Raspberry Pi OS and similar), macOS, or Docker. 64-bit is recommended.
SoftwareNode.js 18 or newer (nodejs.org). Nothing else: the connector is a single file with no other dependencies.
Processor and memoryOne CPU core and 512 MB of memory is the minimum. 2 GB is comfortable. A Raspberry Pi 4 or a small mini-PC is plenty.
DiskAbout 1 GB free. The connector stores no data; its log is written to the console, or to a small file if you use the scripts below.
NetworkWired Ethernet on the same network as the controller (or a VLAN allowed to reach it). A reserved or static LAN address is recommended so the address never changes.
Outbound to ISNHTTPS (port 443) to isnfreewifi.co.za. Nothing else on the internet is needed. The computer must be able to resolve that name through DNS.
Inside your networkHTTPS to the controller: port 443 for a UniFi OS console such as a Cloud Key Gen2 Plus, Dream Machine or Cloud Gateway, or port 8443 for a self-hosted Network application.
InboundNone. Nothing needs to be opened on any firewall.
ClockKeep the clock correct (time sync on). A wrong clock can break secure connections.
Data usedRoughly 0.2 MB an hour for the "anything to do?" calls, plus a status snapshot about once a minute. Expect from a few hundred MB to about 2 GB a month depending on how many guests are connected.
PrivilegesThe connector itself needs no administrator or root rights. Installing the service does, once.
AntivirusNo exclusions are known to be needed. If your security software blocks the program, allow the connector file and outbound HTTPS to isnfreewifi.co.za.
3

The UniFi account the connector uses

Create a dedicated local account on the controller for the connector. Do not use a personal login. The connector signs in by itself, so the account must be local to the controller (not a Ubiquiti cloud sign-in) and must not use two-factor prompts. Choose a password without the characters & | < > ^ % " (the Windows script refuses them).

RoleWhat the connector can do with it
Site Admin (recommended)The full set: let guests in and cut them off, block and unblock devices, restart and upgrade access points, change WiFi names and passwords, speed caps and the read-only security check. Scope it to the site the guest WiFi is on.
Hotspot Operator (smallest)Only authorising and de-authorising guest devices. Enough for a basic guest portal, but it cannot block devices and cannot restart devices or change settings, so those buttons will report an error.
API key (alternative)Newer UniFi Network versions can issue an API key under Network, Control Plane, Integrations, which is Ubiquiti's own supported way to authorise guests. The connector supports it (set UNIFI_API_KEY instead of the user name and password).

Ubiquiti explains its roles on its help centre: UniFi Roles Explained. Role names differ slightly between UniFi OS consoles and the stand-alone Network application. We confirm the exact minimum role for your controller version during your proof of concept.

4

Linux (systemd): Ubuntu, Debian, Raspberry Pi OS and similar

This installs the connector as a service called isn-connector that starts at boot, restarts if it stops, and runs as its own no-login user.

1. Install Node.js and download the connector

Install Node.js 18 or newer, then create the folder and download the connector into it (use the command shown in your dashboard; it works once and expires in 15 minutes):

sudo mkdir -p /opt/isn-connector
sudo curl -fsSL "THE-LINK-FROM-YOUR-DASHBOARD" -o /opt/isn-connector/isn-unifi-connector.js
2. Get the installer and read it

Get the installer and read it before you run it:

curl -fsSL https://isnfreewifi.co.za/isn/connector/install-isn-connector.sh -o install-isn-connector.sh
less install-isn-connector.sh
3. Run the installer

Run it with your controller address and the local account:

sudo UNIFI_URL="https://192.168.1.2" UNIFI_USER="isn-connector" bash install-isn-connector.sh

You are asked for the password, so it never appears in your command history or the system log.

4. Check the download (recommended)

Optional but recommended: confirm the file is the one ISN published. The result must equal this value:

sha256sum install-isn-connector.sh
# expected: 3d0e854a71422c3eca792aa2b9ec9c37a555a956c2bc8f3d41ac367dd22d73a6

Check it with systemctl status isn-connector and watch its log with journalctl -u isn-connector -f. Remove it with sudo bash install-isn-connector.sh --uninstall. The full script, so you can see exactly what it does:

#!/usr/bin/env bash
# ISN UniFi connector: run it as a service that starts by itself after every reboot (Linux, systemd).
#
# 1. Put the connector program in /opt/isn-connector/isn-unifi-connector.js (download it from your ISN dashboard).
# 2. Run this script as root, giving the controller address and the LOCAL UniFi account the connector signs in with.
#    You are asked for the password, so it is never written in your command history or in the system log:
#
#      sudo UNIFI_URL="https://192.168.1.2" UNIFI_USER="isn-connector" bash install-isn-connector.sh
#
#    (Setting UNIFI_PASS on the command line also works, but then the password is recorded in your shell history and the sudo log.)
#
# To remove the service again:   sudo bash install-isn-connector.sh --uninstall
# Read this file before you run it. It only creates the files named below and one systemd service.
set -euo pipefail

APP_DIR=/opt/isn-connector
APP_FILE="$APP_DIR/isn-unifi-connector.js"
ENV_FILE=/etc/isn-connector.env
UNIT_FILE=/etc/systemd/system/isn-connector.service
SERVICE_USER=isn-connector

say() { printf '%s\n' "$*"; }
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

[ "$(id -u)" -eq 0 ] || die "Run this as root, for example: sudo bash $0"
command -v systemctl >/dev/null 2>&1 || die "This script needs systemd. On another system use Docker (see the ISN connector guide)."

if [ "${1:-}" = "--uninstall" ]; then
  systemctl disable --now isn-connector.service 2>/dev/null || true
  rm -f "$UNIT_FILE" "$ENV_FILE"
  systemctl daemon-reload
  say "The ISN connector service was removed. The program file in $APP_DIR was left in place; delete it yourself if you want."
  exit 0
fi

# Node.js 18 or newer is required.
NODE_BIN="$(command -v node || true)"
[ -n "$NODE_BIN" ] || die "Node.js is not installed. Install Node.js 18 or newer first (for example from nodejs.org or your package manager)."
case "$NODE_BIN" in /home/*|/root/*) die "Node.js is installed in a personal folder ($NODE_BIN). Install the system-wide Node.js (your package manager or NodeSource) so the service does not depend on one person's files." ;; esac
NODE_MAJOR="$("$NODE_BIN" -p 'process.versions.node.split(".")[0]')"
[ "$NODE_MAJOR" -ge 18 ] || die "Node.js $NODE_MAJOR found, but 18 or newer is required."

[ -f "$APP_FILE" ] || die "The connector program is missing. Put it at $APP_FILE first (download it from your ISN dashboard)."

UNIFI_URL="${UNIFI_URL:-}"; UNIFI_USER="${UNIFI_USER:-}"; UNIFI_PASS="${UNIFI_PASS:-}"
[ -n "$UNIFI_URL" ]  || die "Set UNIFI_URL, for example https://192.168.1.2"
[ -n "$UNIFI_USER" ] || die "Set UNIFI_USER to the local UniFi account for the connector."
if [ -z "$UNIFI_PASS" ]; then
  [ -t 0 ] || die "Set UNIFI_PASS (or run this in a terminal and type it when asked)."
  read -r -s -p "Password for $UNIFI_USER: " UNIFI_PASS; printf '\n'
fi

# A dedicated account with no login and no home folder: the connector needs no special rights.
id "$SERVICE_USER" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER"
chown -R root:"$SERVICE_USER" "$APP_DIR"; chmod 750 "$APP_DIR"; chmod 640 "$APP_FILE"

# systemd reads this file as root, so only root needs to be able to read it. Quotes and backslashes are escaped for systemd.
esc() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
umask 077
{
  printf 'UNIFI_URL="%s"\n' "$(esc "$UNIFI_URL")"
  printf 'UNIFI_USER="%s"\n' "$(esc "$UNIFI_USER")"
  printf 'UNIFI_PASS="%s"\n' "$(esc "$UNIFI_PASS")"
} > "$ENV_FILE"
chmod 600 "$ENV_FILE"; chown root:root "$ENV_FILE"

cat > "$UNIT_FILE" <<UNIT
[Unit]
Description=ISN UniFi connector
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=$SERVICE_USER
EnvironmentFile=$ENV_FILE
ExecStart=$NODE_BIN $APP_FILE
Restart=always
RestartSec=10
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
UNIT
chmod 644 "$UNIT_FILE"

systemctl daemon-reload
systemctl enable --now isn-connector.service
sleep 3
systemctl --no-pager --lines=8 status isn-connector.service || true
say ""
say "Done. The connector now starts by itself after every reboot and restarts if it stops."
say "Check it:        systemctl status isn-connector"
say "Watch its log:   journalctl -u isn-connector -f"
say "Update it:       replace $APP_FILE with the new download, then: sudo systemctl restart isn-connector"
5

Windows 10, 11 and Windows Server

This registers a scheduled task called ISN UniFi Connector that runs at startup as the system, restarts if it stops, and writes a log file. It needs a PowerShell window opened with Run as administrator.

1. Install Node.js

Install Node.js 18 or newer (64-bit) from nodejs.org.

2. Download the connector

Create the folder and download the connector (the dashboard command; curl.exe is built into Windows 10 and later):

mkdir C:\ProgramData\ISN-Connector
curl.exe -fsSL "THE-LINK-FROM-YOUR-DASHBOARD" -o C:\ProgramData\ISN-Connector\isn-unifi-connector.js
3. Get, check and run the installer

Get the installer, read it, then run it:

curl.exe -fsSL https://isnfreewifi.co.za/isn/connector/install-isn-connector.ps1 -o install-isn-connector.ps1
Get-FileHash .\install-isn-connector.ps1 -Algorithm SHA256   # expected: dffd03246c031de91a6a8c8fd9111bf2507c5e9c68e1694fa6f9edf914b6ead7
notepad install-isn-connector.ps1
powershell -ExecutionPolicy Bypass -File .\install-isn-connector.ps1 -UnifiUrl "https://192.168.1.2" -UnifiUser "isn-connector"

You are asked for the password, so it is never left in your command history. Add -DryRun to see what it would do without changing anything, and -Uninstall to remove the task. The log is C:\ProgramData\ISN-Connector\connector.log. The password is stored in a small runner file in that folder, which the script locks so only Administrators and the system can read it. The full script:

<#
  ISN UniFi connector: run it as a Windows scheduled task that starts by itself after every reboot.

  1. Install Node.js 18 or newer (nodejs.org, 64-bit).
  2. Download the connector program from your ISN dashboard to  C:\ProgramData\ISN-Connector\isn-unifi-connector.js
  3. Open PowerShell as Administrator and run:

       powershell -ExecutionPolicy Bypass -File .\install-isn-connector.ps1 -UnifiUrl "https://192.168.1.2" -UnifiUser "isn-connector"

     (you are asked for the password; or add -UnifiPass 'the-password')

  To remove it again:  .\install-isn-connector.ps1 -Uninstall
  To only show what would be done:  add -DryRun

  Read this file before you run it. It creates one folder under C:\ProgramData, one scheduled task, and nothing else.
#>
[CmdletBinding()]
param(
  [string]$UnifiUrl,
  [string]$UnifiUser,
  [string]$UnifiPass,
  [switch]$Uninstall,
  [switch]$DryRun
)
$ErrorActionPreference = 'Stop'
$AppDir  = Join-Path $env:ProgramData 'ISN-Connector'
$AppFile = Join-Path $AppDir 'isn-unifi-connector.js'
$Runner  = Join-Path $AppDir 'run-connector.cmd'
$LogFile = Join-Path $AppDir 'connector.log'
$TaskName = 'ISN UniFi Connector'

function Say($m) { Write-Host $m }
function Do-It($what, [scriptblock]$action) { if ($DryRun) { Say "[dry run] $what" } else { & $action } }

$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin -and -not $DryRun) { throw 'Run this in a PowerShell window opened with "Run as administrator".' }

if ($Uninstall) {
  Do-It "Stop and remove the scheduled task '$TaskName'" {
    if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false }
    if (Test-Path $Runner) { Remove-Item $Runner -Force }
  }
  Say "The ISN connector task was removed. The folder $AppDir was left in place; delete it yourself if you want."
  return
}

# Node.js 18 or newer
$node = Get-Command node -ErrorAction SilentlyContinue
if (-not $node) { throw 'Node.js is not installed. Install Node.js 18 or newer (64-bit) from nodejs.org first.' }
# The task runs as SYSTEM, so the Node.js it starts must not sit in a folder an ordinary user can change.
if ($node.Source -like "$env:SystemDrive\Users\*") { throw "Node.js is installed in a user folder ($($node.Source)). Install the system-wide Node.js from nodejs.org (it goes in Program Files) and run this again." }
$major = [int](& $node.Source -p "process.versions.node.split('.')[0]")
if ($major -lt 18) { throw "Node.js $major found, but 18 or newer is required." }

if (-not (Test-Path $AppFile) -and -not $DryRun) { throw "The connector program is missing. Download it from your ISN dashboard to $AppFile first." }
if (-not $UnifiUrl)  { throw 'Give -UnifiUrl, for example https://192.168.1.2' }
if (-not $UnifiUser) { throw 'Give -UnifiUser, the local UniFi account for the connector.' }
if (-not $UnifiPass) {
  if ($DryRun) { $UnifiPass = 'dry-run' }
  else {
    $secure = Read-Host -AsSecureString "Password for $UnifiUser"
    $UnifiPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure))
  }
}
# cmd.exe special characters in the values would break the runner file, so refuse them rather than guess.
foreach ($v in @($UnifiUrl, $UnifiUser, $UnifiPass)) { if ($v -match '[&|<>^%"\r\n]') { throw 'The address, user name and password cannot contain these characters: & | < > ^ % " . Pick a password without them for this account.' } }

Do-It "Lock $AppDir to Administrators and SYSTEM, then write $Runner" {
  New-Item -ItemType Directory -Force -Path $AppDir | Out-Null
  # Lock the folder FIRST (and the files already in it, such as the connector program), so nobody else can read the password even for a moment.
  & icacls $AppDir /inheritance:r /grant:r 'SYSTEM:(OI)(CI)F' 'Administrators:(OI)(CI)F' /T | Out-Null
  if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $AppDir, so nothing was written." }
  $lines = @('@echo off', "set UNIFI_URL=$UnifiUrl", "set UNIFI_USER=$UnifiUser", "set UNIFI_PASS=$UnifiPass", "`"$($node.Source)`" `"$AppFile`" >> `"$LogFile`" 2>&1")
  Set-Content -Path $Runner -Value $lines -Encoding ASCII
}

Do-It "Register the scheduled task '$TaskName' (runs at startup as SYSTEM, restarts if it stops)" {
  if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false }
  $action    = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument "/c `"$Runner`""
  $trigger   = New-ScheduledTaskTrigger -AtStartup
  $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
  $settings  = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable -MultipleInstances IgnoreNew -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
  Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'ISN UniFi connector (starts at boot)' | Out-Null
  Start-ScheduledTask -TaskName $TaskName
}

Say ''
Say 'Done. The connector now starts by itself after every reboot and restarts if it stops.'
Say "Check it:       Get-ScheduledTask -TaskName '$TaskName' | Get-ScheduledTaskInfo"
Say "Its log:        Get-Content '$LogFile' -Tail 30"
Say "Update it:      replace $AppFile with the new download, then  Stop-ScheduledTask -TaskName '$TaskName'; Start-ScheduledTask -TaskName '$TaskName'"
6

Docker (any system with Docker)

Put the connector file, the two files below and nothing else in one folder, make the environment file private (chmod 600 isn-connector.env), then run docker compose up -d. The restart: unless-stopped line makes Docker start it after every reboot and after any crash.

isn-connector.env (keep this file private):

UNIFI_URL=https://192.168.1.2
UNIFI_USER=isn-connector
UNIFI_PASS=the-password

compose.yaml:

services:
  isn-connector:
    image: node:20-alpine
    container_name: isn-connector
    restart: unless-stopped
    working_dir: /app
    command: ["node", "/app/isn-unifi-connector.js"]
    volumes:
      - ./isn-unifi-connector.js:/app/isn-unifi-connector.js:ro
    env_file:
      - ./isn-connector.env
7

Prove it survives a reboot

  1. In your dashboard (Network, Connect, UniFi) the connector shows Connected.
  2. Restart the computer. Do not log in to it.
  3. Wait about two minutes. The dashboard shows Connected again by itself.
  4. Press Test connection in the same card. It should say it reached the controller.
  5. Unplug the computer's network cable for a minute and plug it back. The connector retries on its own (it waits a little longer after each failed try, up to 30 seconds) and the dashboard returns to Connected without any action from you.

While the connector is offline ISN cannot tell the controller about new guests, so a guest who signs in at that moment is not let through and may need to sign in again once the connector is back. Guests who are already online are not affected, and the dashboard shows Not connected until it returns.

8

Updating, replacing and removing it

Update or replace the program. In the dashboard press Create a NEW connector secret (the old one stops working), copy the new download command, download the new file over the old one, then restart the service: sudo systemctl restart isn-connector on Linux, Stop-ScheduledTask -TaskName 'ISN UniFi Connector'; Start-ScheduledTask -TaskName 'ISN UniFi Connector' on Windows, or docker compose restart with Docker. Changing the controller address or the account means running the installer again with the new values.

Move it to another computer. Install it there first (new secret, new download), confirm it shows Connected, then remove the old one.

Remove it. Use the --uninstall option (Linux) or -Uninstall option (Windows), or docker compose down. Then delete the connector in the dashboard if you no longer use UniFi with ISN.

9

If it shows "Not connected"

What you seeWhat to check
Log says "could not reach ISN"The computer cannot reach isnfreewifi.co.za on port 443. Test with curl -I https://isnfreewifi.co.za. Check DNS, the firewall and any proxy.
"Forbidden" or HTTP 403 from ISNThe connector secret was replaced. Download the newest connector and restart it.
Cannot reach the controllerCheck the address in UNIFI_URL (use the controller's IP address with https://), the port (443 or 8443) and that the computer is on the right network or VLAN.
Controller says the login is wrongUse a local account, not your Ubiquiti cloud sign-in, with no two-factor prompt. Re-run the installer with the right password.
An action fails but the connector is ConnectedThe account's role is too small for that action (see panel 3). A Hotspot Operator cannot block devices or change settings.
Certificate warningsControllers use their own certificate by default. The connector accepts it unless you set UNIFI_VERIFY_TLS=1.

Still stuck? Send ISN the last 30 lines of the log (journalctl, the log file, or docker logs isn-connector). They contain no passwords. See also Network and routers for the rest of the UniFi setup.