Kahibaro
Discord Login Register

Creating custom services

Understanding Custom systemd Services

Custom services let you control your own scripts and applications with systemd just like built‑in services: start, stop, restart, enable at boot, watch for crashes, and log via journalctl.

In this chapter, you’ll learn how to:

This chapter assumes you already know basic systemctl usage and what a systemd unit is.


Unit File Locations and Naming

Systemd service units usually have names like something.service.

Common locations:

In almost all cases, for your own custom services, you’ll use:

Systemd picks the higher-priority file if the same unit name appears in multiple locations. /etc/systemd/system overrides system-provided units.


A Minimal Custom Service

Here’s a basic example: run a Python script as a system service.

Assume you have:

Create the unit file:

sudo nano /etc/systemd/system/myapp.service

Content:

[Unit]
Description=My example Python service
[Service]
ExecStart=/usr/bin/python3 /opt/myapp/run.py
[Install]
WantedBy=multi-user.target

Then:

sudo systemctl daemon-reload         # reload systemd configs
sudo systemctl start myapp.service   # start now
sudo systemctl status myapp.service  # see status/logs
sudo systemctl enable myapp.service  # start at boot

Key points:

Common `[Service]` Options

The [Service] section is where you describe what to run and how it behaves.

ExecStart and Friends

You can have multiple ExecStartPre= and ExecStartPost= lines; they run in order.

Example with a pre-start check and post-start log:

[Service]
Type=simple
ExecStartPre=/usr/bin/test -f /etc/myapp/config.yml
ExecStart=/usr/bin/myapp --config /etc/myapp/config.yml
ExecStartPost=/usr/bin/logger "myapp started"

If any ExecStartPre= command fails (non-zero exit code), the service will not start.

Service Type

The Type= option tells systemd how your service behaves. Some common ones:

Unless you know otherwise, use Type=simple.

Example oneshot service (runs once at boot):

[Unit]
Description=Initialize myapp at boot
[Service]
Type=oneshot
ExecStart=/usr/local/bin/myapp-init.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

RemainAfterExit=yes tells systemd to consider the service “active” even after the command finishes (useful for boot-time setup actions you may want to check with status).

Restart Policy

Control automatic restarts:

Combine with RestartSec= (delay between restarts):

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

This will wait 5 seconds before attempting to restart after a failure.

Running as a Specific User and Group

By default, system services run as root; that’s often unnecessary and unsafe.

Use User= and optionally Group=:

[Service]
User=myappuser
Group=myappgroup
ExecStart=/usr/bin/myapp

Make sure the user and group exist and have permissions to access required files/directories.


Environment Variables and Working Directory

Setting Environment Variables

You can define environment variables directly:

[Service]
Environment=MYAPP_ENV=production
Environment=PORT=8080
ExecStart=/usr/bin/myapp

For more complex sets, use an environment file:

  MYAPP_ENV=production
  PORT=8080
  LOG_LEVEL=info
  [Service]
  EnvironmentFile=/etc/myapp/myapp.env
  ExecStart=/usr/bin/myapp

Multiple EnvironmentFile= lines are allowed.

WorkingDirectory

If your program expects to run from a specific directory:

[Service]
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 app.py

This is better than embedding cd in a wrapper script.


Controlling Dependencies and Startup Order

Systemd uses targets and dependencies to control when and in what order services start. You typically express relations with After=, Before=, Requires=, and Wants= in the [Unit] section.

Basic Startup Ordering

Example: start after networking is up:

[Unit]
Description=My web app
After=network-online.target
Wants=network-online.target

Wants= indicates a soft dependency: it will try to start network-online.target, but your service doesn’t fail just because that didn’t start. Requires= is a hard dependency: if the required unit fails, your service is stopped.

Common patterns:

  [Unit]
  Description=Custom HTTP service
  Wants=network-online.target
  After=network-online.target
  [Unit]
  Description=My app that uses PostgreSQL
  Requires=postgresql.service
  After=postgresql.service

Requires= ensures PostgreSQL is started and keeps it as a hard dependency.


Installing and Enabling Custom Services

Once you create or modify a unit file, systemd needs to reload its configuration:

sudo systemctl daemon-reload

Then typical workflow:

sudo systemctl start myapp.service      # start now
sudo systemctl status myapp.service     # verify running, check logs
sudo systemctl enable myapp.service     # start automatically on boot
sudo systemctl disable myapp.service    # remove from boot
sudo systemctl stop myapp.service       # stop it

Remember:

User Services vs System Services

You can create services that run as your user without needing root. These are user units.

User Unit Location and Commands

  mkdir -p ~/.config/systemd/user

Example: a user-level script that runs when you log in (for systems using systemd user sessions):

[Unit]
Description=My user-level script
[Service]
Type=simple
ExecStart=/home/alice/bin/my-script.sh
[Install]
WantedBy=default.target

Manage user units with --user:

systemctl --user daemon-reload
systemctl --user start myuserservice.service
systemctl --user enable myuserservice.service
systemctl --user status myuserservice.service

To allow user services to run without an active login (lingering):

sudo loginctl enable-linger alice

Replace alice with your username.


Creating a Wrapper Script for Simplicity

Sometimes your service requires several setup steps that are easier to express in a shell script than in unit options. Example:

Instead of packing all of this into the unit file:

  1. Create a script, e.g. /usr/local/bin/myapp-wrapper.sh:
   #!/usr/bin/env bash
   set -e
   cd /opt/myapp
   source venv/bin/activate
   exec python app.py

Make it executable:

   sudo chmod +x /usr/local/bin/myapp-wrapper.sh
  1. Use it in your unit:
   [Unit]
   Description=My app via wrapper
   [Service]
   ExecStart=/usr/local/bin/myapp-wrapper.sh
   Restart=on-failure
   [Install]
   WantedBy=multi-user.target

The exec in the script replaces the shell process with your app, which allows systemd to track it cleanly.


Using Temporary Files, State, and Logs

Systemd offers options to manage where the service stores data, and to keep it separate from the rest of the filesystem. Some useful ones:

Example:

[Service]
User=myapp
Group=myapp
ExecStart=/usr/bin/myapp --state-dir=/var/lib/myapp
StateDirectory=myapp
RuntimeDirectory=myapp

This will ensure:

You can still add explicit mkdir steps if you prefer, but these tools reduce manual setup.


Basic Security Hardening Options

You can restrict what your custom service can access. A few simple, commonly used options:

Example:

[Service]
User=myapp
Group=myapp
ExecStart=/usr/bin/myapp
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true

Start with simple measures and test thoroughly; strong restrictions can break applications that expect broad access.


Testing and Debugging Your Custom Service

When a custom service doesn’t work, systemd gives useful tools to find out why.

Checking Status

sudo systemctl status myapp.service

This shows:

Viewing Logs

Use journalctl:

sudo journalctl -u myapp.service            # all logs for this unit
sudo journalctl -u myapp.service -f         # follow in real time
sudo journalctl -u myapp.service --since "10 minutes ago"

If your application prints to stdout/stderr, logs appear here automatically.

Checking Unit Syntax

Systemd intentionally has minimal syntax, but you can catch obvious errors by reloading:

sudo systemctl daemon-reload

If something is badly wrong, you’ll see messages in:

journalctl -b -p err

or

sudo systemd-analyze verify /etc/systemd/system/myapp.service

systemd-analyze verify is particularly helpful to catch dependency loops, invalid options, etc.

Temporary Overrides and Testing Changes

While developing, it’s convenient to edit the unit, then:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service
sudo systemctl status myapp.service

Repeat until the service behaves as expected.


Overriding Existing Services (Drop-in Configs)

If you want to change options for an existing service without editing its main unit file (which may be owned by your distribution), use a drop-in:

sudo systemctl edit ssh.service

This opens an editor where you can place only the overrides, for example:

[Service]
Environment=SSH_LOG_LEVEL=VERBOSE

This creates something like /etc/systemd/system/ssh.service.d/override.conf.

To remove the override:

sudo systemctl revert ssh.service

Using drop-ins for your own services is optional, but it’s useful when you’d rather not edit the original file in place.


Practical Example: Custom Web App Service

Putting several concepts together, here’s a more complete example service for a simple web app.

Assume:

Unit file: /etc/systemd/system/webapp.service

[Unit]
Description=Simple Web Application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp
EnvironmentFile=/etc/webapp/webapp.env
ExecStart=/opt/webapp/start.sh
Restart=on-failure
RestartSec=5
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable webapp.service
sudo systemctl start webapp.service
sudo systemctl status webapp.service

If it fails, check logs:

sudo journalctl -u webapp.service -f

Creating custom systemd services mostly comes down to defining what you want to run, under which user, when it should start and restart, and how it depends on other parts of the system. With a few unit files and some testing via systemctl and journalctl, you can turn your scripts and applications into robust, boot-managed services.

Views: 24

Comments

Please login to add a comment.

Don't have an account? Register now!