Auto-provisioning makes provisioned devices wait for NTP at every boot: is it safe to skip systemd-time-wait-sync?

Hello Toradex team,

I hope you are all doing great! And thank you for your support.

On our devices that are already provisioned, multi-user.target comes only after the first NTP sync. We think the cause is the stock auto-provisioning.service. With a drop-in that skips the wait, multi-user.target comes about 17 s earlier on Torizon OS 7 and about 32 s earlier on TorizonCore 6. We would like to know if this drop-in is safe.

Hardware:

uname:

  • Torizon OS 7: 6.6.142-7.7.0 #1-Torizon SMP PREEMPT Fri Jun 19 08:16:23 UTC 2026 aarch64
  • TorizonCore 6: 5.15.209-6.8.9+git.3342fc7bf0a9 #1-TorizonCore SMP PREEMPT Thu Jun 18 14:03:37 UTC 2026 aarch64

Images tested:

  • torizon-docker-verdin-imx8mp-Tezi_7.7.0+build.40.tar, customized with TorizonCore Builder. systemd 255.21
  • torizon-core-docker-verdin-imx8mp-Tezi_6.8.9+build.46.tar, customized with TorizonCore Builder. systemd 250.14

Provisioning:

  • Both images: online auto-provisioning (provisioning: mode: online in tcbuild.yaml)
  • All tests on devices that are already provisioned

Issue:

On a provisioned device, multi-user.target comes just after the first NTP sync: at 33.2 s on Torizon OS 7.7.0, and at 46.6 s on TorizonCore 6.8.9 (one boot each, office network). Both versions behave the same.

Until then, graphical.target waits too, and systemctl is-system-running stays starting.

On a network that blocks NTP (UDP 123), multi-user.target would never come, because systemd-time-wait-sync.service has TimeoutStartSec=infinity.

What we found in the unit file:

The stock /usr/lib/systemd/system/auto-provisioning.service is the same on 6.8.9 and 7.7.0:

[Unit]
Description=Automatically provision the device to the Platform Services
After=network-online.target systemd-time-wait-sync.service
Wants=network-online.target
Requires=systemd-time-wait-sync.service
ConditionPathExists=/var/sota/auto-provisioning.json
ConditionPathExists=!/var/sota/import/pkey.pem

[Install]
WantedBy=multi-user.target
  • The unit is enabled, so every boot queues it. Requires= queues systemd-time-wait-sync.service with it.
  • systemd checks a unit’s conditions only when its job is about to run, after its After= units. So the two ConditionPathExists= lines do not prevent the wait: systemd-time-wait-sync runs to completion first, and only then is auto-provisioning skipped.
  • A target is ordered after the units it wants. So multi-user.target waits for auto-provisioning.service, which waits for the first NTP sync.

What the wait does not delay, on our images:

  • aktualizr-torizon.service is not ordered after time-sync.target. Its After= holds network-online.target, nss-lookup.target and boot-complete.target. So the update client never waited for this.
  • Nothing is ordered after time-sync.target: systemctl show time-sync.target -p Before gives only shutdown.target.
  • Our application containers (docker-compose.service) do not wait for multi-user.target.

So the wait delays multi-user.target and what comes after it, not the application.

What we tested:

A drop-in, /etc/systemd/system/systemd-time-wait-sync.service.d/override.conf:

[Unit]
RequiresMountsFor=/var/sota/import
ConditionPathExists=!/var/sota/import/pkey.pem
  • The condition uses the same marker as the stock auto-provisioning.service. A device that is not provisioned yet still waits for NTP before it provisions, since TLS needs a correct clock. A provisioned device skips the wait. A unit skipped on a condition still satisfies Requires=.
  • RequiresMountsFor= is needed, because systemd-time-wait-sync.service has DefaultDependencies=no. Without it, systemd checked the condition at 4.77 s, before /var was mounted (5.57 s), saw no key and ran the wait anyway. With it, the check comes after the mount (5.85 s vs 5.83 s) and the wait is skipped.

Result:

Time to multi-user.target, from the journal (Reached target Multi-User System):

Stock (one boot) With the drop-in (mean of 10 boots) Range of the 10 boots
Torizon OS 7.7.0 33.2 s 15.9 s 13.8 to 17.1 s
TorizonCore 6.8.9 46.6 s 14.5 s 13.5 to 17.0 s
  • systemd-time-wait-sync reported ConditionResult=no on all 20 boots.
  • The NTP sync still happens, in the background: about 32 s after boot on 7.7.0 (31.9 to 32.7 s over the same 10 boots).
  • multi-user.target is now gated by network-online.target (systemd-analyze critical-chain).

Questions:

  1. Is the wait after provisioning intended? Does any Torizon component (aktualizr, remote access, tzn-mqtt, docker, …) expect time-sync.target, or multi-user.target, to mean “the clock is synced” on a provisioned device?
  2. Is this drop-in a safe way to skip the wait? Do you recommend another marker than /var/sota/import/pkey.pem, or another approach?
  3. Could the stock image do the same, for example with this condition shipped as a drop-in in the auto-provisioning recipe?

Our plan for now: keep this drop-in in our image, unless you see a problem with it.

I can share the boot journals and the systemd-analyze output of the 20 boots if useful.

Best regards,
M

Hi, I’m the Toradex AI Assistant.

I might make mistakes or occasionally get you wrong. Our support team will step in if needed.


Your analysis is thorough and well-documented. Based on the available Torizon OS documentation, I can provide some context, though the specific question about skipping systemd-time-wait-sync on already-provisioned devices isn’t directly addressed in the sources.

What the Documentation Says About Time Synchronization

The production programming documentation mentions that the auto-provisioning service can be customized following normal systemd procedures(1). It specifically notes:

Customizations to the service follow the normal procedures as with any other systemd service. Reasons for customizing the service would include limiting the amount of retries performed by the service or changing the delay between retries, to say a few.(1)

The documentation provides an example of creating a systemd override:

# systemctl edit auto-provisioning

And mentions you can capture such overrides using TorizonCore Builder(1).

Time Synchronization Requirements

The CODESYS licensing documentation emphasizes the importance of correct time for license operations:

A working network connection with Network Time Protocol (NTP) synchronization. An incorrect clock locks the license container, so check it before you start.(2)

It also shows that time-sync.target is used to ensure the clock is synchronized before critical operations:

The time-sync.target dependency holds the daemon until the clock synchronizes. On a module without a Real-Time Clock (RTC), a daemon that starts too early creates the license container with the wrong time and locks it.(2)

Regarding Your Approach

Your drop-in approach appears sound from a systemd perspective. However, I cannot confirm from the available documentation whether:

  1. Any Torizon component explicitly depends on time-sync.target or multi-user.target implying clock synchronization on provisioned devices
  2. The specific marker /var/sota/import/pkey.pem is the recommended way to detect provisioning status
  3. Whether this should be integrated into the stock image

For definitive answers on these architectural questions and to validate your drop-in is safe for production use, I recommend visiting the Toradex Community or GitHub to discuss with the Torizon platform team directly.

Your testing methodology is excellent, and the 20-boot validation provides good confidence. The key consideration would be ensuring no Torizon service assumes time synchronization at multi-user.target, which only the platform maintainers can confirm authoritatively.

Best regards,
Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.