Alright, let’s get our hands dirty with the actual guts of a systemd service file: the [Unit], [Service], and [Install] sections. This is where you stop describing your service and start commanding it. Think of it as writing a very specific, very pedantic set of instructions for a hyper-competent but utterly literal-minded robot butler.

The [Unit] Section: Your Service’s Public Relations Manager

This section isn’t about running the process; it’s about describing it to the world (and to other units). It’s the metadata block. Here’s what you absolutely need to know.

Description is non-negotiable. Write a clear, concise one-liner here. It’s what everyone sees when they run systemctl status, so make it useful. “Web server” is bad. “Acme Corp API v2 Service” is good.

Documentation is your friend. Throw a URL in here. A year from now, when you’ve forgotten how this cursed thing works, you’ll thank your past self.

Now for the big ones: After and Requires. This is how you create dependencies. After specifies ordering: “Start after these other units.” Requires is stricter: “If this other unit fails or stops, tear me down too.” You almost always want After without the brutal strictness of Requires. For example, your service might need the network to be up, but if the network drops later, you might not want your service to be killed. For that, use Wants—a gentler “it’d be nice if this other unit is around” without the murderous consequences.

[Unit]
Description=My Brilliant Go Web Service
Documentation=https://github.com/you/your-brilliant-service
After=network.target
Wants=network.target
# Requires=network.target # Probably overkill, use Wants

The [Service] Section: The Bossy Micro-Manager

This is where the real magic (and horror) happens. You’re now telling systemd how to run your process.

Type is critical and everyone gets it wrong at least once. The most common types are simple (the default) and forking.

  • Use simple if your process runs in the foreground and doesn’t daemonize. This is how most modern apps written in Go, Node.js, or Python (with proper signal handling) should run. systemd expects the process it starts to be the main one.
  • Use forking if your application is an old-school C daemon that backgrounds itself by, well, forking. The classic double-fork nonsense. If you use this, you must also set PIDFile= so systemd can figure out which of the forked children is actually the main process it should track. It’s a mess, and I’m judging the developers of that software.

ExecStart is the command itself. Use absolute paths. Always. Your $PATH is a lie to systemd. And for the love of all that is holy, do not put your flags directly in ExecStart. Use EnvironmentFile or drop-in files (.conf) in /etc/systemd/system/your.service.d/ for configuration. It makes your life infinitely easier.

Restart and RestartSec are your safety net. Restart=on-failure is usually what you want. It tells systemd to relaunch your service if it exits with a non-zero code. RestartSec is the breath it takes before trying again. Setting it to something like 5s prevents a rapid crash loop from hammering your system.

[Service]
Type=simple
# Let's say we source our flags from a file kept out of version control
EnvironmentFile=/etc/opt/brilliant-service.conf
ExecStart=/usr/local/bin/brilliant-service --flag=$SOME_FLAG_FROM_ENV_FILE
# Let's be honest, your code might still have bugs. This handles them gracefully.
Restart=on-failure
RestartSec=5s
# Your process might need to write things. This is safer than running as root.
User=brilliant-service
Group=brilliant-service
# For better isolation, but that's a whole other chapter.
# ProtectHome=read-only
# ProtectSystem=strict

The [Install] Section: The “Where Does This Belong?” Footnote

This section is ignored at runtime. It only matters when you use systemctl enable. It tells systemd where to plop the symlink to your service file so it starts on boot.

WantedBy is the one directive you’ll use 99.9% of the time. WantedBy=multi-user.target means “please start this service when the system reaches a state where multiple users can log in, which is a perfectly reasonable state for a server to be in.” It’s almost always the right answer.

[Install]
WantedBy=multi-user.target

Without this section, systemctl enable will just shrug at you. It’s the footnote that makes the whole book work on boot.