Tailscale certificate automation on TrueNAS (AI generated script)

I would like to share an AI generated script that I successfully used to automate my TrueNAS certificates with Tailscale.

This guide shows how to automatically use a Tailscale HTTPS certificate for the TrueNAS SCALE Web UI, when Tailscale runs inside a Docker container.


Overview

What this does

  1. Runs tailscale cert inside a Docker container

  2. Writes the cert/key to a host bind-mount

  3. Imports the cert into TrueNAS

  4. Applies it to the Web UI

  5. Restarts the UI

  6. Runs automatically via cron


Requirements

  • TrueNAS SCALE

  • Docker

  • A running Tailscale container (tailscaled)

  • A host directory bind-mounted into the container at /certs


Step 1 – Create a certificate directory on the host

Create a dataset or directory on your pool:

mkdir -p /mnt/<pool>/Applications/tailscale-certs
chmod 700 /mnt/<pool>/Applications/tailscale-certs

Step 2 – Bind-mount it into the Tailscale container

Your Tailscale container must mount the host directory to /certs. This can be done by editing the Tailscale App and adding a Host Path.

Conceptually:

Host path:      /mnt/<pool>/Applications/tailscale-certs
Container path: /certs

This is required so the TrueNAS host can read the files generated by tailscale cert.


Step 3 – Create the automation script (generic)

Save this as:

/mnt/<pool>/scripts/import_tailscale_cert.sh

Script

#!/bin/bash
set -euo pipefail

# =========================
# USER CONFIG (REQUIRED)
# =========================

CONTAINER_NAME="__TAILSCALE_CONTAINER_NAME__"
TS_HOSTNAME="__TAILSCALE_DNS_NAME__"
HOST_CERT_DIR="__HOST_CERT_DIR__"
LOG_FILE="__LOG_FILE__"
TRUENAS_CERT_NAME="__TRUENAS_CERT_NAME__"

# =========================

CRT="${HOST_CERT_DIR}/ts.crt"
KEY="${HOST_CERT_DIR}/ts.key"

# Cron-safe PATH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# Log from inside the script (cron may discard output)
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
exec >>"$LOG_FILE" 2>&1
echo "----- $(date -Is) starting Tailscale cert import -----"

# Dependency checks
command -v docker >/dev/null || { echo "ERROR: docker not found"; exit 2; }
command -v jq >/dev/null || { echo "ERROR: jq not found"; exit 2; }
command -v midclt >/dev/null || { echo "ERROR: midclt not found"; exit 2; }

# Ensure container is running
docker ps --format '{{.Names}}' | grep -qx "$CONTAINER_NAME" || {
  echo "ERROR: container not running: $CONTAINER_NAME"
  exit 2
}

# Ensure /certs mount exists
docker exec "$CONTAINER_NAME" sh -lc 'test -d /certs' || {
  echo "ERROR: /certs not mounted in container"
  exit 2
}

# Request or renew certificate
docker exec "$CONTAINER_NAME" sh -lc \
  "tailscale cert --cert-file /certs/ts.crt --key-file /certs/ts.key \"$TS_HOSTNAME\""

# Verify files on host
[[ -s "$CRT" && -s "$KEY" ]] || {
  echo "ERROR: certificate files missing on host"
  exit 2
}

# Import into TrueNAS certificate store
midclt call certificate.create "$(jq -n \
  --arg n "$TRUENAS_CERT_NAME" \
  --rawfile c "$CRT" \
  --rawfile k "$KEY" \
  '{name:$n, create_type:"CERTIFICATE_CREATE_IMPORTED", certificate:$c, privatekey:$k}')" >/dev/null || true

# Look up certificate ID by name (robust across TrueNAS versions)
CERT_ID="$(midclt call certificate.query | jq -r \
  --arg n "$TRUENAS_CERT_NAME" '.[] | select(.name==$n) | .id' | tail -n 1)"

[[ -n "$CERT_ID" ]] || {
  echo "ERROR: failed to locate imported certificate"
  exit 2
}

# Apply certificate to Web UI and restart UI
midclt call system.general.update "$(jq -n --argjson id "$CERT_ID" \
  '{ui_certificate:$id, ui_restart_delay:1}')" >/dev/null
midclt call system.general.ui_restart >/dev/null

echo "SUCCESS: Web UI certificate updated"


Step 4 – Make the script executable

chmod 700 /mnt/<pool>/scripts/import_tailscale_cert.sh

Step 5 – Run once manually

/usr/bin/bash /mnt/<pool>/scripts/import_tailscale_cert.sh

A short Web UI disconnect is expected.


Step 6 – Verify in the UI

  • System Settings → Certificates

  • System Settings → General → GUI → Web Interface HTTPS Certificate

Confirm the new certificate is selected.


Step 7 – Create the cron job

TrueNAS UI → System Settings → Advanced → Cron Jobs → Add

Command

/usr/bin/bash /mnt/<pool>/scripts/import_tailscale_cert.sh

You can find the script on my Github repository:

1 Like

Thanks for sharing the script! I’m amazed it’s not standard behaviour to be honest.

The script could be optimised by not copying nor restarting anything when the certificate has not changed. Maybe I will take some time to do it later. :sweat_smile:

I would be more specific for beginners (I have like 30 minutes of TrueNas experience here :-p)

So, in the hope it can help anyone :

First install tailscale from the apps. It is running in docker already.

1) CONTAINER_NAME=“TAILSCALE_CONTAINER_NAME”

=> run “docker ps” in the shell to get the container name

in case of error try adding your user in the docker group by issuing the following command :
sudo usermod -aG docker truenas_admin

2) TS_HOSTNAME=“TAILSCALE_DNS_NAME”

=> It would be your full dns name including the “ts.net” part.

3) HOST_CERT_DIR="/mnt/pool1-mirror/main-apps/tailscale-certs"

4) LOG_FILE="/mnt/pool1-mirror/main-apps/tailscale-certs/tslog.log"

=> Just an example here for info

5) TRUENAS_CERT_NAME=“truenas_cert”

This will also be the name that appears later on in the GUI.

I hope that helps anyone.

1 Like

Thanks for the feedback. I also added the ability of deletion of expired certificates.