name: systemd-autostart description: 'Auto-start ROS 2 with systemd. Use when creating service files for robot boot, managing dependencies, or viewing logs.'
Auto-Starting ROS 2 Nodes on Boot with systemd
Basic Service File
Create /etc/systemd/system/my-robot.service:
[Unit]
Description=Robot ROS 2 Bringup
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=robot
Group=robot
# Source ROS 2 and workspace, then launch
ExecStart=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup my_robot.launch.py'
# Environment variables
Environment="ROS_DOMAIN_ID=0"
Environment="RMW_IMPLEMENTATION=rmw_fastrtps_cpp"
Environment="ROS_LOG_DIR=/home/robot/.ros/log"
# Restart policy
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5
# Resource limits
LimitNOFILE=65536
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=my-robot
[Install]
WantedBy=multi-user.target
Managing the Service
# Reload after editing any .service file
sudo systemctl daemon-reload
# Enable to start on boot
sudo systemctl enable my-robot.service
# Manual start/stop/restart
sudo systemctl start my-robot.service
sudo systemctl stop my-robot.service
sudo systemctl restart my-robot.service
# Check status
sudo systemctl status my-robot.service
# View logs (follow mode)
journalctl -u my-robot.service -f
# View logs since last boot
journalctl -u my-robot.service -b
# View last 100 lines
journalctl -u my-robot.service -n 100
Multi-Service Architecture
Split the robot into ordered services for reliability. If hardware drivers crash, only they restart—navigation stays up if topics are still published.
Hardware Drivers (starts first)
/etc/systemd/system/robot-hardware.service:
[Unit]
Description=the robot Hardware Drivers (Teensy, LIDAR, IMU)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=robot
ExecStart=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup sub_launch/hardware.launch.py'
Environment="ROS_DOMAIN_ID=0"
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
Navigation (depends on hardware)
/etc/systemd/system/robot-navigation.service:
[Unit]
Description=the robot Navigation Stack
After=robot-hardware.service
Requires=robot-hardware.service
[Service]
Type=simple
User=robot
ExecStart=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup sub_launch/navigation.launch.py'
Environment="ROS_DOMAIN_ID=0"
# Give navigation stack time to initialize before considering it failed
TimeoutStartSec=30
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Perception (independent of navigation)
/etc/systemd/system/robot-perception.service:
[Unit]
Description=the robot Perception (OAK-D, Object Detection)
After=robot-hardware.service
Wants=robot-hardware.service
[Service]
Type=simple
User=robot
ExecStart=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch oakd_detector detector.launch.py'
Environment="ROS_DOMAIN_ID=0"
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Using an Environment File
Instead of inline Environment= lines, centralize variables:
/etc/my_robot/robot.env:
ROS_DOMAIN_ID=0
RMW_IMPLEMENTATION=rmw_fastrtps_cpp
ROS_LOG_DIR=/home/robot/.ros/log
RCUTILS_COLORIZED_OUTPUT=0
Reference it in the service:
[Service]
EnvironmentFile=/etc/my_robot/robot.env
Graceful Shutdown
ROS 2 lifecycle nodes need clean shutdown signals. systemd sends SIGTERM by default, which rclpy and rclcpp handle correctly. For extra safety:
[Service]
# Send SIGTERM, wait 15 seconds, then SIGKILL
TimeoutStopSec=15
KillMode=mixed
KillSignal=SIGTERM
# Optional: run a shutdown script
ExecStop=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 lifecycle set /nav2_controller shutdown'
Pre-Start Checks
Run a hardware check before launching:
[Service]
ExecStartPre=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup precheck.launch.py'
ExecStart=/bin/bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup my_robot.launch.py'
Common Issues and Fixes
Missing hardware permissions
# Add the service user to required groups
sudo usermod -aG dialout robot # Serial ports (Teensy, LIDAR)
sudo usermod -aG video robot # Cameras
sudo usermod -aG i2c robot # I2C sensors
sudo usermod -aG gpio robot # GPIO pins (Raspberry Pi)
Service starts before devices are ready
[Unit]
# Wait for specific udev device
After=dev-ttyACM0.device
Requires=dev-ttyACM0.device
Or use a startup delay:
[Service]
ExecStartPre=/bin/sleep 5
No display server for GUI nodes
If a node needs DISPLAY (e.g., debugging with RViz):
[Service]
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/robot/.Xauthority"
This is generally not needed for headless robot operation.
Logging fills disk
# Limit journal size
sudo journalctl --vacuum-size=500M
# Or in /etc/systemd/journald.conf:
# SystemMaxUse=500M
Debugging a Failing Service
# See why it failed
systemctl status my-robot.service
journalctl -u my-robot.service --no-pager -n 50
# Test the ExecStart command manually as the service user
sudo -u robot bash -c '\
source /opt/ros/jazzy/setup.bash && \
source /home/robot/ros2_ws/install/setup.bash && \
ros2 launch my_robot_bringup my_robot.launch.py'
# Check environment resolution
systemctl show my-robot.service --property=Environment
Enable/Start All Robot Services at Once
sudo systemctl enable robot-hardware robot-navigation robot-perception
sudo systemctl start robot-hardware robot-navigation robot-perception
# Or create a target that groups them:
# /etc/systemd/system/my-robot.target
# [Unit]
# Description=the robot Robot Full Stack
# Requires=robot-hardware.service robot-navigation.service robot-perception.service
# After=robot-hardware.service
# [Install]
# WantedBy=multi-user.target