32blogby StudioMitsu
yocto7 min read

Yocto systemd Auto-Start: Setup Guide with 7 Pitfalls

Add systemd services to Yocto with auto-start and timers. Includes 7 common pitfalls, based on Scarthgap 5.0 LTS.

yoctoembedded-linuxsystemdscarthgap
On this page

You've built your application with Yocto. Now you need it to start automatically on boot. In embedded Linux, that means creating a systemd service.

But getting systemd services to work correctly in Yocto requires three things to align: the distro configuration, the recipe, and the service file. If any one of them is missing, your service silently fails to start.

This guide covers everything from adding systemd services to 7 common pitfalls, based on Yocto Scarthgap 5.0 LTS (systemd 255).

Enabling systemd with INIT_MANAGER

Yocto defaults to sysvinit as the init system. You need to explicitly switch to systemd.

INIT_MANAGER Approach (Zeus 3.0+)

Add a single line to conf/local.conf or conf/distro/mydistro.conf.

bash
# conf/local.conf
INIT_MANAGER = "systemd"

This one line loads init-manager-systemd.inc, which automatically configures the following.

bash
# Contents of init-manager-systemd.inc (Scarthgap branch)
DISTRO_FEATURES:append = " systemd usrmerge"
DISTRO_FEATURES_BACKFILL_CONSIDERED:append = " sysvinit"
VIRTUAL-RUNTIME_init_manager ??= "systemd"
VIRTUAL-RUNTIME_initscripts ??= "systemd-compat-units"
VIRTUAL-RUNTIME_login_manager ??= "shadow-base"
VIRTUAL-RUNTIME_dev_manager ??= "systemd"
ROOT_HOME ?= "/root"

The Legacy Approach (Pre-Zeus)

Before Zeus, you had to manually set DISTRO_FEATURES and VIRTUAL-RUNTIME_* variables in local.conf. This still works but the one-line INIT_MANAGER approach is simpler.

Verifying the Configuration

bash
# Check that systemd is in DISTRO_FEATURES
bitbake-getvar DISTRO_FEATURES | grep systemd

Writing a systemd Service Recipe

To add a systemd service in Yocto, write a recipe that inherits the systemd bbclass.

Complete Recipe Example

bash
# meta-mylayer/recipes-apps/myapp/myapp_1.0.bb
SUMMARY = "My custom application"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://myapp.sh \
           file://myapp.service"

S = "${WORKDIR}"

inherit systemd

SYSTEMD_SERVICE:${PN} = "myapp.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service"

Key Variables

VariablePurposeDefault
SYSTEMD_SERVICE:${PN}Service file name(s) to manage${PN}.service
SYSTEMD_AUTO_ENABLE:${PN}Whether to auto-enableenable
SYSTEMD_PACKAGESPackages to manage with systemd${PN}

What inherit systemd does automatically:

  • Generates a postinst script that runs systemctl enable
  • Creates symlinks under multi-user.target.wants/
  • Generates a prerm script for package removal

Guarding Your Recipe for systemd

To prevent build errors on distros without systemd, use REQUIRED_DISTRO_FEATURES.

bash
REQUIRED_DISTRO_FEATURES = "systemd"

This causes the recipe to be skipped when DISTRO_FEATURES doesn't include systemd. If another recipe depends on it, a build error is raised, making it easy to trace the root cause.

Adding to an Existing Recipe via bbappend

If you need to add a systemd service to an existing recipe, use a bbappend file. See Pattern 3 in the bbappend practical guide for the specifics. Note that inherit systemd cannot be used in .bbappend files — the base .bb recipe must already inherit the systemd class.

Designing Service Files for Embedded

How you write the service file directly affects startup reliability. Here are the key points for embedded Linux.

Basic Structure

ini
# myapp.service
[Unit]
Description=My Application Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

[Unit] Section

After= controls startup ordering. A common mistake with network-dependent services is using After=network.target.

  • network.target — indicates network devices have been configured. Doesn't guarantee an IP address has been assigned
  • network-online.target — indicates the network is usable. Use this when your service needs to make API calls or network connections

[Service] Section

Type= specifies how the process starts.

TypeUse Case
simpleForeground process (default)
forkingDaemon that forks into the background
oneshotScript that runs once and exits

For embedded, set Restart=on-failure with RestartSec=5 (retry after 5 seconds) to enable automatic recovery from crashes.

Permissions

Install service files with 0644. Adding execute permissions (0755) can cause systemd to emit warnings.

bash
# Correct
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# Wrong (execute permission is unnecessary)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

Timer and Socket Activation Recipes

systemd's strengths go beyond services. Timers handle periodic tasks, and socket activation enables on-demand startup.

Timer Units

Use systemd timers instead of cron to keep dependencies and logs integrated with systemd.

ini
# myapp.timer
[Unit]
Description=Run myapp periodically

[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
Persistent=true

[Install]
WantedBy=timers.target
ini
# myapp.service (triggered by the timer)
[Unit]
Description=My Application Task

[Service]
Type=oneshot
ExecStart=/usr/bin/myapp --run-task

In the recipe, list both files in SYSTEMD_SERVICE.

bash
SRC_URI = "file://myapp.sh \
           file://myapp.service \
           file://myapp.timer"

SYSTEMD_SERVICE:${PN} = "myapp.service myapp.timer"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
    install -m 0644 ${WORKDIR}/myapp.timer ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service \
                ${systemd_system_unitdir}/myapp.timer"

Socket Activation

Socket activation delays service startup until an actual connection arrives. This is effective for resource-constrained embedded devices.

ini
# myapp.socket
[Unit]
Description=My Application Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target
ini
# myapp.service (started by the socket)
[Unit]
Description=My Application
Requires=myapp.socket

[Service]
Type=simple
ExecStart=/usr/bin/myapp

The recipe syntax is the same as for timers. List both the service and socket in SYSTEMD_SERVICE.

bash
SYSTEMD_SERVICE:${PN} = "myapp.service myapp.socket"

7 Common Pitfalls and Fixes

These are the most frequent issues reported on Yocto mailing lists and forums.

1. systemd Not in DISTRO_FEATURES

The most common mistake. Even with inherit systemd in your recipe, if your distro configuration doesn't have systemd enabled, nothing works.

bash
# Check
bitbake-getvar DISTRO_FEATURES | grep systemd

# Fix: add to conf/local.conf
INIT_MANAGER = "systemd"

2. Missing WantedBy in [Install] Section

If the service file has no [Install] section or is missing WantedBy=, systemctl enable does nothing. No symlink is created, and the service won't auto-start.

ini
# Required
[Install]
WantedBy=multi-user.target

3. Missing :$ on SYSTEMD_SERVICE

bash
# Wrong (no package qualifier)
SYSTEMD_SERVICE = "myapp.service"

# Correct
SYSTEMD_SERVICE:${PN} = "myapp.service"

SYSTEMD_SERVICE is a per-package variable. Without :${PN}, the systemd bbclass can't correctly generate the postinst script.

4. Installing to /etc/systemd/system/

bash
# Wrong (/etc/ is for user overrides)
install -d ${D}/etc/systemd/system/

# Correct
install -d ${D}${systemd_system_unitdir}
# → /usr/lib/systemd/system/

/etc/systemd/system/ is where users locally override units. Units installed by packages belong in ${systemd_system_unitdir} (/usr/lib/systemd/system/).

5. Expecting Network Availability with network.target

ini
# network.target: indicates network devices are configured
# ≠ network is actually usable

# When you need actual network connectivity
[Unit]
After=network-online.target
Wants=network-online.target

network.target only means interfaces have been configured. It doesn't guarantee IP assignment or DNS resolution.

6. Execute Permissions on Service Files

bash
# Wrong (systemd may emit warnings)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# Correct
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

Service files are configuration files, not executables. 0644 is the correct permission.

7. journald Logs Lost on Reboot

By default, journald stores logs in RAM (/run/log/journal/). Logs disappear on reboot.

To persist logs, create the /var/log/journal/ directory in your recipe.

bash
do_install:append() {
    install -d ${D}${localstatedir}/log/journal
}
FILES:${PN} += "${localstatedir}/log/journal"

For embedded devices, consider Flash write frequency. Frequent log writes shorten Flash lifespan. Set limits with SystemMaxUse= and MaxRetentionSec= in journald.conf.

Wrapping Up

Getting systemd services to work in Yocto requires three things.

  1. Distro configuration: Enable with INIT_MANAGER = "systemd"
  2. Recipe: inherit systemd and set SYSTEMD_SERVICE:${PN}
  3. Service file: Include WantedBy=multi-user.target in the [Install] section

Timer and socket activation let you integrate periodic tasks and on-demand startup into systemd as well.

Most pitfalls come down to missing configuration. When things don't work, start by checking DISTRO_FEATURES with bitbake-getvar and running systemctl status myapp on the target. The build error debugging guide covers these tools in detail.

For a deeper dive into systemd recipes and Yocto recipe development, these books are solid references.

PRMastering Embedded Linux Development 4th Ed (2024)View on Amazon
PREmbedded Linux Development Using Yocto Project 3rd Ed (2023)View on Amazon