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.targetEnable 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.serviceAfter any edit to the .service file, always run daemon-reload before restarting.
Key Fields
Type=simple— Default; useType=oneshotif the script runs a single task and exitsAfter=network-online.target— Waits for network before runningExecStartPre=/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 pathsEnvironment— Set environment variables inline (noexport, no quotes unless value contains spaces)EnvironmentFile=/etc/environment— Load variables from an external file instead of inlineRestart=on-failure/RestartSec=30s— Retry on failure after a delay; stops retrying after successStandardOutput/StandardError— Redirect logs to a fileWantedBy=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_hereDo 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/environmentThe file must use plain KEY=VALUE format — no export, no variable expansion, no spaces around =.
MY_VAR=/etc/environmentis wrong — this sets the variable to the literal string/etc/environment. UseEnvironmentFile=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 120Combining 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.shCommon Errors
status=1/FAILURE— The script ran but errored; checkjournalctlfor the specific messageStart request repeated too quickly— The service crashed in a loop; addStartLimitIntervalSec=0to[Unit]to prevent lockout while debuggingPermission denied— Either the script isn’t executable (chmod +x) orWorkingDirectoryis missing and the script writes to/- Environment variable not set — Systemd doesn’t inherit shell environment; use
Environment=orEnvironmentFile= - Command not found — Use absolute paths inside scripts (e.g.
/usr/bin/gitnotgit); find paths withwhich <command>