Skip to Content
Bash & LinuxCustom Systemd Service

Custom Systemd Service

Template for creating a systemd service unit that runs a script at boot with environment variable handling, startup delays, and logging.

Service File

Create a .service file in /etc/systemd/system/:

[Unit] Description=Run custom script at startup After=network-online.target Wants=network-online.target [Service] Type=simple User=<your-user> Group=<your-user> # Wait 2 minutes for VPN / network dependencies ExecStartPre=/usr/bin/sleep 120 WorkingDirectory=/home/<your-user>/path/to/script/ Environment=MY_SECRET=<secret-value> Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ExecStart=/home/<your-user>/path/to/project/your-script.sh Restart=on-failure RestartSec=30s StandardOutput=append:/var/log/your-service.log StandardError=append:/var/log/your-service.log [Install] WantedBy=multi-user.target

Enable and Start

# Make the script executable chmod +x /home/<your-user>/path/to/project/your-script.sh # Reload systemd, enable on boot, and start now sudo systemctl daemon-reload sudo systemctl enable your-service.service sudo systemctl start your-service.service

After any edit to the .service file, always run daemon-reload before restarting.

Key Fields

  • Type=simple — Default; use Type=oneshot if the script runs a single task and exits
  • After=network-online.target — Waits for network before running
  • ExecStartPre=/usr/bin/sleep 120 — Adds a startup delay (useful for VPN or slow network dependencies)
  • WorkingDirectory — Sets the working directory; without this, systemd defaults to / which causes permission errors for relative paths
  • Environment — Set environment variables inline (no export, no quotes unless value contains spaces)
  • EnvironmentFile=/etc/environment — Load variables from an external file instead of inline
  • Restart=on-failure / RestartSec=30s — Retry on failure after a delay; stops retrying after success
  • StandardOutput/StandardError — Redirect logs to a file
  • WantedBy=multi-user.target — Runs at normal boot (multi-user mode)

Environment Variables

Systemd does not load .bashrc or .profile, so scripts may not find variables or commands available in an interactive shell.

Option A — Inline in the service file:

Environment=MY_SECRET=your_value_here

Do not wrap the value in quotes unless it contains spaces. Systemd can include the literal quote characters in the value.

Option B — Load from an external file:

EnvironmentFile=/etc/environment

The file must use plain KEY=VALUE format — no export, no variable expansion, no spaces around =.

MY_VAR=/etc/environment is wrong — this sets the variable to the literal string /etc/environment. Use EnvironmentFile= to read the contents of a file.

Startup Delays

If the service depends on something not yet ready at boot (e.g. a VPN), add a delay:

ExecStartPre=/usr/bin/sleep 120

Combining this with Restart=on-failure and RestartSec=30s handles cases where the delay isn’t long enough — the script retries until it succeeds, then stops.

Debugging

# Check service status sudo systemctl status your-service.service # View detailed logs journalctl -u your-service.service --no-hostname --since "1 hour ago" # Test the script as the service user sudo -u <your-user> /path/to/your/script.sh

Common Errors

  • status=1/FAILURE — The script ran but errored; check journalctl for the specific message
  • Start request repeated too quickly — The service crashed in a loop; add StartLimitIntervalSec=0 to [Unit] to prevent lockout while debugging
  • Permission denied — Either the script isn’t executable (chmod +x) or WorkingDirectory is missing and the script writes to /
  • Environment variable not set — Systemd doesn’t inherit shell environment; use Environment= or EnvironmentFile=
  • Command not found — Use absolute paths inside scripts (e.g. /usr/bin/git not git); find paths with which <command>
Last updated on