Provisioning Troubleshooting
How to read a ctrld provisioning failure, what each failure code means, and how to fix it.
Organizations OnlyMass provisioning is an Organization feature. This page covers failures from a provisioning code, RMM script, or MDM profile.
Overview
When a ctrld install fails to provision, it always reports the same fixed code, on every channel. This page shows you where to find that code, what each code means, and what to do next. Most codes you can fix yourself, without a support ticket.
Step 1: Find the failure code
Every provisioning failure logs one fixed line:
provisioning failed: stage=<stage> code=<CODE> (exit <N>)
Where you see it depends on how you deployed:
-
macOS MDM (.pkg): the line is in
/var/log/install.logon the device, prefixedctrld postinstall:.sudo grep 'ctrld postinstall:' /var/log/install.log -
RMM script,
/dlone-liner, or manualctrld start: the line prints straight to the output of the command or script that ran the install.
The RMM script's own preflight checks (a bad MODE value, a bad CUSTOM_UID) and an invalid /dl intercept_mode parameter (HTTP 400) run before ctrld starts. None of them produce a stage=... code=... line. Instead, the script's or the API's own error text names the bad argument.
On every channel, ctrld also keeps a result file for Support: provision_result.json, next to the persisted ctrld.log. The default path is /etc/controld/provision_result.json on macOS and Linux. On Windows, it sits next to ctrld.exe. The file never contains the provisioning token.
Step 2: Run ctrld diag
Before you do anything else, run:
sudo ctrld diagAdd --json for a machine-readable version:
sudo ctrld diag --jsondiag reports the client version, the MDM-managed preferences (macOS only), the last provisioning result, the service state, and whether the Control D API is reachable. It never requires a service to be installed, and it never prints the provisioning token — only whether one is present. It is safe to paste into a support ticket as-is.
Failure codes
| Code | What it means | What to do |
|---|---|---|
PROVISION_TOKEN_MALFORMED | The provisioning code is not shaped like one: too short, too long, or it has stray whitespace. | Copy the code again from the dashboard. Paste it with no extra spaces or line breaks. |
CUSTOM_HOSTNAME_INVALID | The custom hostname contains a character a hostname cannot use. | Fix the hostname: letters, digits, hyphens, and dots only, 3–64 characters. |
INTERCEPT_MODE_INVALID | The intercept mode value is not one this channel accepts. | Use a valid value for the channel. /dl script: dns or hard. MDM profile: standard or intercept-dns. |
API_UNREACHABLE | ctrld did not reach the Control D API after several retries. | Make sure that DNS, proxy, and firewall rules allow the device to reach api.controld.com. The service retries on its own once the network recovers. |
API_REJECTED | The API rejected the request outright (bad token or bad request). | Make sure that the provisioning code and org settings in the dashboard are correct, then re-run the install. |
API_DEVICE_INVALID | The API reports this device's endpoint no longer exists. | Re-provision the device with a current, active provisioning code. |
TOKEN_INVALID | The API does not recognize this provisioning code. | Create or select an active provisioning code, then re-download the profile or script. |
TOKEN_EXPIRED | The provisioning code passed its expiry date. | Create or select an active provisioning code, then re-download the profile or script. |
TOKEN_LIMIT_REACHED | The provisioning code reached its device limit. | Free a device slot, or create a new code, then re-download the profile or script. |
TOKEN_DISABLED | The provisioning code was invalidated. | Create or select an active provisioning code, then re-download the profile or script. |
LISTENER_BIND_FAILED | ctrld did not bind any DNS listener address. Every fallback address was already taken. | Free port 53 on the device, or make sure that the configured address exists on the device. sudo lsof -i :53 -nP shows what uses it. |
LISTENER_CONFIGURED_ADDR_UNAVAILABLE | The listener address you configured cannot be bound, and ctrld will not fall back to another one. | Make sure that the listed address exists on the device, and that nothing else uses it. |
SERVICE_INSTALL_FAILED | The OS refused to install the ctrld service. | Make sure that no permission or MDM policy issue blocks new services, then contact support with your ctrld diag output. |
SERVICE_START_FAILED | The service installed but did not start. | Contact support with your ctrld diag output. |
SERVICE_SELFCHECK_FAILED | The service started but failed its own health check, and rolled itself back. | Contact support with your ctrld diag output. |
UNCLASSIFIED | A failure that does not yet have its own code. | Contact support with your ctrld diag output. |
PROFILE_PREFS_MISSING | macOS package install only. The MDM configuration profile with the provisioning token never reached the device before the package installed. | Make sure that the profile is scoped to this device, then re-push the package. |
SERVICE_RELOAD_FAILED | macOS package upgrade only. The new package installed, but the active service did not reload. | Run sudo launchctl load /Library/LaunchDaemons/ctrld.plist, or re-push the package. |
Before you deploy
Before you roll out a provisioning code, make sure that the settings below are correct. This avoids the input-stage failures listed above.
Provisioning token
- Length: 6–64 characters.
- Current codes start with
org-v1-(for exampleorg-v1-EXAMPLE1234). Some older codes do not have this prefix. That is not a failure on its own.
Custom hostname
ctrld makes sure that the custom hostname is valid before it makes any network call. It uses a two-tier rule:
- Letters, digits, and hyphens: kept exactly as typed.
- Dots:
ctrldaccepts them, but the Control D server can fold or strip dots when it registers the device. The endpoint name in the dashboard can then differ slightly from what you requested. - Any other character (spaces,
+,@,_, and so on): fails the install withCUSTOM_HOSTNAME_INVALID, on the channel where you typed the value.
Also: 3–64 characters, and a valid RFC1123 hostname shape (no leading or trailing hyphen, no empty label). Some setups depend on an exact hostname match downstream. If yours does, make sure that the endpoint name in the dashboard matches what you expect after the device enrolls.
Intercept mode
The accepted values depend on where you set intercept mode — these channels do not all share a vocabulary:
| Channel | Accepted values |
|---|---|
MDM configuration profile (InterceptMode key) | standard, intercept-dns |
RMM script one-liner (MODE argument) | standard, intercept-dns |
/dl install script or URL parameter | dns, hard |
Use the value set for the channel you deploy through. If you use a value from a different channel, ctrld reports INTERCEPT_MODE_INVALID.
Stale profiles and scripts
When you edit or invalidate a provisioning code, it does not change a profile or install script you already downloaded or deployed. Devices that already enrolled continue to work. Only new enrollments through the old code stop.
After you change a provisioning code:
- Re-download the configuration profile or install script.
- Re-push it to your fleet.
An MDM or RMM console that shows an old profile or script after a code change is stale, not broken. Re-download and re-push to pick up the change.
Updated about 1 hour ago
