TrueNAS Cloud Sync Provisioning SOP
This SOP documents how DTC provisions a TrueNAS appliance (CORE or SCALE) with Backblaze B2 cloud-backed offsite backup via Cloud Sync Tasks. It covers NinjaOne NMS onboarding, device GUID generation, bucket provisioning, scoped application key creation, Cloud Sync Task configuration, and ZFS snapshot setup.
For the architectural principles behind the bucket naming, credential model, and identity scheme, see Cloud Backup Architecture Standards in Standards & Configuration. Read that page first if you haven't. This SOP is the procedural implementation of the architecture defined there.
1. Purpose
Provision a TrueNAS appliance for automated, encrypted, immutable offsite backup to Backblaze B2 via native Cloud Sync Tasks. Produces a running backup pipeline with:
- Scoped B2 application key restricted to the provisioned bucket
- Bucket with object lock (compliance mode), SSE-B2 encryption, and a 30-day lifecycle rule
- Cloud Sync Task per root pool, running daily with remote encryption enabled
- Hourly ZFS snapshots on the TrueNAS side with 14-day retention
- Device GUID stored as a ZFS user property and mirrored in NinjaOne
- All client credentials filed in the client's IT Glue record (DTC-internal credentials such as the B2 admin key remain in 1Password)
2. Scope
Applies to:
- Any DTC-managed TrueNAS appliance (CORE or SCALE, DSM-equivalent versions out of scope — those are covered by the Synology SOP)
- Clients requiring per-location offsite backup with immutability
- New provisioning and re-provisioning after device moves between orgs or locations
Does not cover:
- TrueNAS initial install, pool creation, SMB/NFS share setup (those are separate SOPs)
- Restore workflows (covered separately)
- Windows-side Veeam provisioning (see Veeam BDR Deployment SOP)
3. Prerequisites
3.1 Device readiness
- TrueNAS appliance installed, networked, and reachable
- At least one ZFS pool created (typical:
pool0or named per client convention) - Admin credentials for the TrueNAS web UI and SSH
- SSH access enabled on the box (required for ZFS user property commands)
3.2 NinjaOne prerequisites
The org and location must exist in NinjaOne with populated UUID custom fields:
| Level | Custom field | Must contain |
|---|---|---|
| Organization | Org UUID |
Valid UUIDv4 for the client org |
| Location | Location UUID |
Valid UUIDv4 for the physical location |
| Device | Device GUID (to be populated by this SOP) |
Will be written during provisioning |
If the org or location UUID fields are empty, populate them before proceeding. NinjaOne is the source of truth for org/location identity; the rest of the provisioning is downstream of it.
3.3 Backblaze B2 access
- B2 admin account credentials (from DTC 1Password vault)
- B2 account has bucket quota headroom — check current bucket count against the raised account limit
b2-windows.exeor equivalent B2 CLI available on your workstation (or the provisioning script handles this)
3.4 Credential vaults
- DTC 1Password access — for the B2 admin/master key and other DTC-internal credentials
- Client IT Glue access — all per-client credentials generated by this SOP (scoped B2 application key, encryption password and salt, SNMPv3 creds) are stored in the client's IT Glue record
- Client IT Glue record exists or will be created during provisioning
- You know the IT Glue path for the client's Backup Credentials section
4. Security Considerations
- Compliance-mode object lock cannot be overridden even by the B2 root account. Double-check the bucket settings before the first backup runs — enabling compliance mode is irreversible.
- Scoped application keys are restricted to one bucket. Never reuse a key across buckets. Never use the B2 admin key for TrueNAS Cloud Sync Tasks.
- Encryption password and salt are stored in the client's IT Glue record only. They are NEVER stored on the TrueNAS box itself (TrueNAS holds them in its config DB but that's the box's own memory of them). If the IT Glue entry is lost, the backup data is unrecoverable.
- SNMPv3 only for NMS onboarding. SNMPv1/v2c are forbidden per DTC security policy. Use randomly generated usernames, auth passwords, and privacy secrets per device.
- Never commit credentials to git, ticket notes, or chat. Reference the IT Glue entry by URL/path for client credentials, or the 1Password entry for DTC-internal credentials.
5. Procedure
Phase 1: NinjaOne NMS Onboarding
Goal: get the TrueNAS box into NinjaOne as a monitored device with SNMPv3, so we have a NinjaOne device record to attach the Device GUID to.
Step 1.1: Generate SNMPv3 credentials
Generate random values for the SNMPv3 user, auth password, and privacy secret. Any cryptographically random generator works. Recommended:
# PowerShell
$snmpUser = -join ((65..90) + (97..122) | Get-Random -Count 12 | ForEach-Object {[char]$_})
$snmpAuthPass = -join ((33..126) | Get-Random -Count 24 | ForEach-Object {[char]$_})
$snmpPrivKey = -join ((33..126) | Get-Random -Count 24 | ForEach-Object {[char]$_})
Write-Host "User: $snmpUser"
Write-Host "Auth: $snmpAuthPass"
Write-Host "Priv: $snmpPrivKey"
Store these three values in the client's IT Glue record under a password labeled TrueNAS — NMS Credentials with the device hostname.
Step 1.2: Configure SNMPv3 on the TrueNAS box
In the TrueNAS web UI:
- System Settings → Services → SNMP (SCALE) or Services → SNMP (CORE)
- Configure:
- SNMP v3 Support: Enabled
- Username: value from 1.1
- Authentication Type: SHA
- Password: auth password from 1.1
- Privacy Protocol: AES
- Privacy Passphrase: privacy secret from 1.1
- Community: leave blank (disables v1/v2c)
- Auto-start: Enabled
- Save and start the service.
Step 1.3: Add the device to NinjaOne NMS
In NinjaOne:
Once the device appears green in NinjaOne, you have a device record to bind the Device GUID to.
Phase 2: Device GUID Generation
Goal: generate a Device GUID, store it as a ZFS user property on the TrueNAS pool, and mirror it into the NinjaOne device custom field.
Step 2.1: Check for an existing Device GUID
SSH to the TrueNAS box and check whether the box already has a Device GUID on its root pool:
zfs get -H -o value com.dtctoday:device-guid <poolname>
Replace <poolname> with the actual pool (e.g. pool0). Use zpool list to see pool names.
- If the output is
-or empty: no GUID exists, proceed to 2.2. - If the output is a valid UUID: skip to 2.3.
Step 2.2: Generate and store a new Device GUID
Native TrueNAS command:
DEVICE_GUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
echo "Generated: $DEVICE_GUID"
zfs set com.dtctoday:device-guid=$DEVICE_GUID <poolname>
Verify:
zfs get -H -o value com.dtctoday:device-guid <poolname>
Should return the same UUID you just generated.
Note: the Device GUID is set on the root pool only. If the box has multiple pools, do not set the property on every pool — it belongs to the device as a whole, and the root pool is its canonical home. The property survives pool export/import, so if the device is rebuilt and the pool is re-imported, the GUID comes back with it.
Step 2.3: Write the Device GUID to NinjaOne
In NinjaOne, open the TrueNAS device record and set the Device GUID device-level custom field to the UUID from step 2.2 or 2.1.
The Device GUID is now stored in two places: on the device itself (via ZFS property) and in NinjaOne (via custom field). Both must match. If they ever diverge, the NinjaOne value wins — update the device property to match NinjaOne, not the other way around.
Phase 3: Compute Bucket and Folder Names
Goal: from the location UUID and device GUID, compute the bucket name and folder path that this TrueNAS box will use. (The org UUID is no longer needed to compute the name — the location OUID implies the client.)
Step 3.1: Gather inputs
From NinjaOne:
- Org UUID (org custom field)
- Location UUID (location custom field for the location this device belongs to)
From the device (step 2):
- Device GUID
Step 3.2: Compute bucket name
Formula:
truenas-{location-uuid-no-dashes-lowercase}
The org is not part of the bucket name (see Cloud Backup Architecture Standards — the location OUID implies the client, and a sold or transferred location keeps its bucket). Only the location OUID and the workload token are needed.
PowerShell helper:
function Get-TrueNASBucketName {
param(
[Parameter(Mandatory)][string]$LocationUuid
)
$locFlat = ($LocationUuid -replace '-', '').ToLower()
return "truenas-$locFlat"
}
Get-TrueNASBucketName -LocationUuid "a1b2c3d4-e5f6-7071-8293-a4b5c6d7e8f9"
# Returns: truenas-a1b2c3d4e5f670718293a4b5c6d7e8f9
Length check: bucket name must be 50 characters or fewer. truenas- plus a 32-char flat OUID is 40 chars, which fits. If you get a longer result, something is wrong with the input UUID — stop and investigate.
Step 3.3: Compute folder path
For each root pool on the TrueNAS box, the folder path is:
/{device-guid-dashed}/{pool-name}
One folder per pool. The leading slash is required by TrueNAS Cloud Sync's folder field.
Example for a box with pool0 and pool1:
/9babab19-acc0-4587-aa05-ccd8103a1148/pool0
/9babab19-acc0-4587-aa05-ccd8103a1148/pool1
Each pool gets its own Cloud Sync Task pointing at its own folder path.
Phase 4: Backblaze B2 Bucket Provisioning
Goal: create the bucket in B2 with all the standard settings from the architecture standards page.
Step 4.1: Create the bucket
Using the B2 CLI (authenticate with the DTC admin key first):
b2 authorize-account <admin-key-id> <admin-app-key>
b2 create-bucket \
--defaultServerSideEncryptionAlgorithm "AES256" \
--defaultServerSideEncryption "SSE-B2" \
--fileLockEnabled \
<bucket-name> \
allPrivate \
--lifecycleRules '[{"daysFromHidingToDeleting":30,"daysFromUploadingToHiding":null,"fileNamePrefix":""}]'
Replace <bucket-name> with the computed name from step 3.2.
Step 4.2: Enable object lock in compliance mode
Object lock must be set after bucket creation via the B2 API (CLI support is limited for this). Use the B2 native API:
b2 update-bucket \
--defaultRetentionMode compliance \
--defaultRetentionPeriod "14 days" \
<bucket-name> \
allPrivate
Verify the bucket shows "defaultRetention": {"mode": "compliance", "period": {"duration": 14, "unit": "days"}} in b2 get-bucket <bucket-name> output.
Compliance mode is irreversible. Double-check the bucket name before running this.
Step 4.3: Create the scoped application key
b2 create-key \
--bucket <bucket-name> \
<bucket-name> \
"listBuckets,readBuckets,writeBuckets,deleteBuckets,listFiles,readFiles,writeFiles,deleteFiles,readBucketEncryption,writeBucketEncryption,readBucketRetentions,writeBucketRetentions,readFileLegalHolds,writeFileLegalHolds,readFileRetentions,writeFileRetentions,bypassGovernance"
The key name is the bucket name (for grep-ability). The key is restricted to the bucket. Capture the output:
<keyId> <applicationKey>
Save both values. You will not be able to retrieve the application key after this moment.
Step 4.4: Generate the encryption password and salt
Cloud Sync's Remote Encryption feature needs two additional secrets separate from the B2 credentials:
$encryptionPassword = -join ((33..126) | Get-Random -Count 32 | ForEach-Object {[char]$_})
$encryptionSalt = -join ((33..126) | Get-Random -Count 32 | ForEach-Object {[char]$_})
Or on the TrueNAS box:
openssl rand -base64 32
openssl rand -base64 32
These are NOT stored on the device outside of TrueNAS's own config DB. They must be captured and stored in the client's IT Glue record before the Cloud Sync Task is saved, or the data becomes unrecoverable.
Step 4.5: Store all credentials in IT Glue
Create or update the client's IT Glue record with a password labeled TrueNAS Cloud Backup — {Location Name} containing these fields:
| Field | Value |
|---|---|
| Bucket Name | from step 3.2 |
| B2 Key ID | from step 4.3 |
| B2 Application Key | from step 4.3 |
| Encryption Password | from step 4.4 |
| Encryption Salt | from step 4.4 |
| Device GUID | from step 2 |
| Notes | Device hostname, TrueNAS management IP, provisioning date |
Phase 5: Cloud Sync Task Configuration
Goal: configure one Cloud Sync Task per root pool on the TrueNAS box, matching DTC standards exactly.
Step 5.1: Create the B2 cloud credential in TrueNAS
In the TrueNAS web UI:
- Credentials → Backup Credentials → Cloud Credentials → Add
- Name: bucket name (from step 3.2) — this becomes the credential label
- Provider: Backblaze B2
- Key ID: from step 4.3
- Application Key: from step 4.3
- Click Verify Credential — must succeed before saving
- Save
Step 5.2: Create the Cloud Sync Task
- Data Protection → Cloud Sync Tasks → Add
Configure exactly as follows:
| Field | Value |
|---|---|
| Description | {PoolName} Cloud Backup (e.g. Pool0 Cloud Backup) |
| Direction | PUSH |
| Transfer Mode | SYNC |
| Directory/Files | /mnt/{pool-name} (e.g. /mnt/pool0) |
| Credential | bucket name (from step 5.1) |
| Bucket | bucket name (from step 3.2) |
| Folder | /{device-guid-dashed}/{pool-name} (from step 3.3) |
| Schedule | Daily at 00:00 (0 0 * * *) |
| Enabled | checked |
Expand Advanced Options:
| Field | Value |
|---|---|
| Take Snapshot | unchecked (relies on separate periodic snapshots, see Phase 6) |
| Follow Symlinks | unchecked |
| Pre-script | empty |
| Post-script | empty |
| Exclude | /Archive/** and /archive/** (one per line — both cases) |
Expand Advanced Remote Options:
| Field | Value |
|---|---|
| Upload Chunk Size (MiB) | 96 |
Use --fast-list |
checked |
| Remote Encryption | checked — on |
| Filename Encryption | unchecked — off |
| Encryption Password | from step 4.4 |
| Encryption Salt | from step 4.4 |
| Bandwidth Limit | Per Backup & Data Protection Standards — Backup Network Throttling: 20% of client uplink during business hours, 80% off-hours. Calculate Mbps from the client's measured/contracted upload speed. Configure as an rclone-style schedule, e.g. 00:00,80M 08:00,20M 18:00,80M for a client with 100 Mbps uplink during 08:00–18:00 business hours. Adjust schedule windows to the client's actual operating hours. |
| Transfers | default (leave blank unless tuning) |
Save the task.
Verify the exclude entries after saving — TrueNAS occasionally reformats them. Both /Archive/** and /archive/** must appear in the exclude list. The double-case exclusion catches both naming conventions since some clients capitalize and some don't.
Step 5.3: Repeat for additional pools
If the box has multiple root pools (e.g. pool0 and pool1), create one Cloud Sync Task per pool. Each task:
- Has its own Directory/Files pointing at the pool mount path
- Uses the same bucket and credential (one bucket per device, many pools share it)
- Has its own Folder with the pool name as the trailing segment (
/{device-guid}/{pool-name})
One Cloud Sync Task per pool — do not combine multiple pools into a single task.
Phase 6: ZFS Snapshot Task
Goal: configure native ZFS snapshots on the TrueNAS side for point-in-time recovery independent of the cloud sync. Snapshots run hourly and retain for 14 days.
Step 6.1: Create the snapshot task
In TrueNAS:
- Data Protection → Periodic Snapshot Tasks → Add
Configure:
| Field | Value |
|---|---|
| Dataset | {pool-name} (root of each pool — one task per pool) |
| Recursive | checked |
| Exclude | (leave blank unless there's a dataset that must not be snapshotted) |
| Snapshot Lifetime | 2 weeks |
| Naming Schema | auto-%Y-%m-%d_%H-%M |
| Schedule | Hourly (0 * * * *) |
| Begin / End | 00:00 / 23:59 (full-day coverage) |
| Allow Taking Empty Snapshots | unchecked |
| Enabled | checked |
Save.
Step 6.2: Verify snapshots start rolling
Wait up to one hour for the first snapshot, then check:
zfs list -t snapshot | grep auto-
You should see auto-{date}-{time} snapshots appearing on the pool and all its child datasets.
Phase 7: Test Run and Verification
Goal: kick off the first Cloud Sync run and verify it completes and lands data in B2 as expected.
Step 7.1: Trigger a manual run
In TrueNAS:
- Data Protection → Cloud Sync Tasks
- Click the Run Now button on the task (play icon)
- Watch the task status transition to RUNNING
On a fresh large pool, the initial sync can take hours to days. This is expected — the first upload is the full corpus. Subsequent runs are incremental.
Step 7.2: Verify data lands in B2
While the sync is running or after it completes:
b2 ls --long <bucket-name>/<device-guid-folder>/<pool-name>
You should see files appearing under the expected folder path. Verify that the Archive exclusions are honored — no Archive/ directories should appear in the B2 listing.
Step 7.3: Verify encryption
Pick one file from the B2 listing and download it:
b2 download-file-by-name <bucket-name> <file-path> /tmp/test-download
file /tmp/test-download
If Remote Encryption is working, the downloaded file is not the original plaintext — it's rclone's encrypted blob. Restoring requires going back through TrueNAS's Cloud Sync with the encryption password and salt. This is the correct behavior.
If you can open the downloaded file as plaintext, Remote Encryption is misconfigured. Stop, investigate, and fix before the backup is considered operational.
Step 7.4: Document completion
Update the client's IT Glue record with the provisioning date and "operational" status. Update the HaloPSA ticket with:
- Bucket name
- Device GUID
- First successful backup timestamp
- Total data size transferred on first run
- Reference to this SOP
6. Verification Checklist
Before marking the TrueNAS box as "operational" for cloud backup, verify every item:
- TrueNAS box visible in NinjaOne NMS with SNMPv3 only (no v1/v2c)
- Device GUID stored as
com.dtctoday:device-guidZFS property on root pool - Device GUID mirrored in NinjaOne device custom field
- B2 bucket created with correct name:
truenas-{org-flat}-{loc-short} - Bucket has object lock enabled in compliance mode
- Bucket has SSE-B2 enabled
- Bucket lifecycle rule: delete hidden versions after 30 days
- Scoped application key restricted to this bucket only
- B2 Key ID and App Key stored in client's IT Glue record
- Encryption Password and Salt stored in client's IT Glue record
- Cloud Sync Task: PUSH + SYNC mode
- Cloud Sync Task source:
/mnt/{pool-name}(per pool) - Cloud Sync Task folder:
/{device-guid}/{pool-name}(per pool) - Cloud Sync Task schedule: Daily at 00:00
- Take Snapshot: unchecked (handled by Phase 6 periodic snapshots)
- Exclude: both
/Archive/**and/archive/** - Upload Chunk Size: 96 MiB
- Use
--fast-list: checked - Remote Encryption: on
- Filename Encryption: off
- Bandwidth Limit configured per Backup & Data Protection Standards (20% biz hours / 80% off-hours of client uplink)
- Periodic Snapshot Task: hourly, 14-day retention, recursive, per pool
- First manual sync completed successfully
- Archive folders confirmed absent from B2 listing
- Encryption verified via downloaded file inspection
- HaloPSA ticket updated with bucket name, device GUID, first-run timestamp
7. Troubleshooting
First sync stalls or errors
Check the task log in TrueNAS (Data Protection → Cloud Sync Tasks → Task → Logs). Common causes:
- B2 credential verification failed: re-verify the credential in Credentials → Backup Credentials. Key ID or App Key is wrong, or the key was scoped to the wrong bucket.
- Object lock preventing deletion: rclone's sync logic may try to delete destination-side files that the source no longer has. Compliance mode will block deletion of locked objects. This is normal for files under the 14-day lock window — rclone logs will show "access denied" for retention-locked objects. Not a failure; the file simply persists longer than the source.
- Network saturation during business hours: first-run syncs are large. If the daily schedule is firing during dental practice hours, data can back up. Check bandwidth utilization and consider an off-hours start for the first run.
Archive folder is appearing in B2 despite the exclude
Verify the exclude field content after saving the task. TrueNAS has a known habit of reformatting multi-line entries. Both /Archive/** and /archive/** must appear as separate lines. Also verify your source path — if the source is /mnt/pool0/Archive instead of /mnt/pool0, the exclude is meaningless because the scope IS Archive.
Bucket name conflict on create-bucket
If b2 create-bucket returns "bucket name already exists," another DTC provisioning run may have created the bucket already (e.g. someone provisioned the same device or location twice). Check:
b2 get-bucket <bucket-name>
If the existing bucket is yours (same org/location), you can skip the create step and proceed to the key creation. If it belongs to a different account, investigate — the UUID inputs may be wrong.
Device GUID mismatch between ZFS property and NinjaOne
The NinjaOne value wins. Update the ZFS property to match NinjaOne:
zfs set com.dtctoday:device-guid=<ninja-value> <poolname>
Then re-run the provisioning script to verify everything still computes correctly.
Encryption password lost
If the IT Glue entry for the encryption password/salt is lost, the backup data is unrecoverable. rclone cannot decrypt without the password. The only recovery is to re-seed from the source and start the cloud backup chain over. Treat IT Glue entries for backup encryption as business-critical.
8. Related
- Cloud Backup Architecture Standards — architectural principles this SOP implements
- Backup & Data Protection Standards — backup network throttling pillar (20% biz hours / 80% off-hours)
- Approved Credential Storage — DTC-internal vs client credential vault map
- Veeam BDR Deployment SOP — Windows-side equivalent for Veeam BDR boxes
- Synology NAS — Google Workspace Backup Configuration SOP — Synology equivalent for Google Workspace data
msp-script-libraryrepo — for automation scripts (currently Veeam; TrueNAS scripts to follow this SOP's design)- IT Glue — primary store for per-client credentials (scoped B2 keys, encryption password/salt, SNMPv3 creds)
- DTC 1Password vault — DTC-internal credentials only (B2 admin/master key)
- NinjaOne — device records and org/location UUID custom fields