Table of Contents
Introduction
Creating custom services with systemd allows you to run your own programs as managed background services. Once you define a service unit, systemd can start it automatically during boot, restart it after failures, stop it cleanly, and integrate it with logging and dependencies. In this chapter you will learn how to write simple unit files, place them in the correct locations, reload systemd, and control your new services with common commands.
Where systemd Service Files Live
Systemd reads unit files from several directories. For custom services you create yourself, the most common locations are:
/etc/systemd/system for system wide units that override distribution defaults or define new services for all users.
~/.config/systemd/user for per user services managed by the user instance of systemd.
System unit files typically require root privileges to create and manage, because they affect the whole system and are controlled with systemctl as root. User unit files are created and controlled by normal users and use systemctl --user.
Systemd also has directories such as /usr/lib/systemd/system or /lib/systemd/system where distribution packages install their unit files. You normally do not create or edit files there for custom services. Custom services should go into /etc/systemd/system or the user configuration directory, because those locations are meant for local configuration and override packaged units if they share the same name.
Basic Structure of a Service Unit
A systemd service unit is a text file with sections and key value pairs. It follows the common .ini style syntax. A typical service unit has three main sections: [Unit], [Service], and [Install].
The unit file name should end with .service. For example, you might create /etc/systemd/system/myapp.service.
Here is a minimal example of a system service file:
[Unit]
Description=My sample application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.target
In the [Unit] section, Description is a short human readable summary. You can add other metadata such as dependencies, but those are part of systemd fundamentals and are covered separately.
The [Service] section defines how the process is started and how systemd should treat it. The most important directive is ExecStart that sets the exact command to execute. This should be the full path to your program or script, possibly with arguments. You must not use shell style redirection or pipes directly here; if you need a shell, you can call /bin/sh -c '...' explicitly.
The [Install] section tells systemd how to enable the service. The WantedBy line associates the unit with one or more targets so that systemctl enable can set up the right symlinks. For typical long running system services that should start in multi user mode, WantedBy=multi-user.target is appropriate.
Important rule: Every service unit must define an ExecStart in the [Service] section. Without it the service cannot be started.
Creating a Simple Custom Service
To create a new system wide service, login as root or use sudo and create a file in /etc/systemd/system.
For example, suppose you installed a custom script at /usr/local/bin/hello_server that runs indefinitely and listens on some port. To run it as a service, you can write:
sudo nano /etc/systemd/system/hello.serviceThen place content such as:
[Unit]
Description=Hello TCP server
[Service]
ExecStart=/usr/local/bin/hello_server
Restart=on-failure
User=hello
Group=hello
[Install]
WantedBy=multi-user.target
In this example, Restart=on-failure tells systemd to restart the service if the process exits with a non zero status or a crash occurs. The User and Group directives specify that the service should not run as root but as the hello user and group. That user and group must already exist on the system.
Once the file is saved, you must tell systemd that unit files have changed.
Reloading systemd and Managing a New Service
Systemd caches unit definitions in memory. After adding or editing unit files in /etc/systemd/system you need to reload systemd so that it reads the new configuration.
You reload units with:
sudo systemctl daemon-reloadThis does not stop or start any services. It only makes systemd reparse the unit files.
After a reload you can start the new service:
sudo systemctl start hello.serviceTo verify its status:
systemctl status hello.serviceYou can stop it with:
sudo systemctl stop hello.serviceTo have the service start automatically at boot, you enable it:
sudo systemctl enable hello.serviceYou can also combine enabling and starting:
sudo systemctl enable --now hello.serviceTo disable automatic starting at boot while leaving the unit file in place:
sudo systemctl disable hello.serviceChoosing the Right Service Type
Different programs behave differently when started. Some run in the foreground and never exit until stopped. Others fork into the background and detach from the terminal. Systemd needs to know what to expect so it can track the correct process. This is controlled by the Type directive in the [Service] section.
The most commonly used types for custom services are:
Type=simple which assumes the process started by ExecStart is the main long running process and stays in the foreground. This is the default if you omit Type. It is suitable for most modern daemons and long running scripts that do not fork themselves into the background.
Type=forking which indicates that the service will call fork() itself and that the parent process will exit early, leaving a child process running in the background. This matches traditional Unix style daemons that detach from the terminal. Systemd expects the original process to exit when startup is complete and considers the child as the main process.
Type=oneshot which is intended for short lived tasks that run once and then exit successfully. These do not keep running as background services. They are useful for custom initialization tasks.
For a typical custom application you write or a script that just runs some server loop, Type=simple is appropriate and you do not need to specify it explicitly.
If you have a legacy daemon that daemonizes itself, you set:
[Service]
Type=forking
ExecStart=/usr/local/sbin/old_daemonFor a setup task that should run at boot and then complete, you can write:
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup_task
RemainAfterExit=yes
With RemainAfterExit=yes, systemd will treat the service as active even after the process exits successfully, which can be useful for dependency chains.
Important rule: If your program already daemonizes itself, you must not use Type=simple. Use Type=forking so systemd can track the correct process.
Running Scripts as Services
You can run custom shell, Python, or other scripts as services. The main requirement is that the script must run in the foreground and not exit immediately if it is intended as a long running service.
Assume you have a shell script /usr/local/bin/log_time.sh that appends timestamps to a file in an infinite loop. It has a proper shebang like #!/bin/bash at the top and executable permissions.
You can define a service:
[Unit]
Description=Time logger
[Service]
ExecStart=/usr/local/bin/log_time.sh
Restart=always
User=logger
[Install]
WantedBy=multi-user.target
Restart=always tells systemd to restart the script no matter how it exits. Be careful with this setting, because a script that exits immediately will cause rapid restarts. You can combine it with RestartSec to introduce a delay:
Restart=always
RestartSec=5
If your script relies on environment variables or a specific working directory, you can use directives such as Environment and WorkingDirectory in the [Service] section:
[Service]
WorkingDirectory=/opt/myapp
Environment=ENVIRONMENT=production
ExecStart=/opt/myapp/start.sh
If you need more complex environment loading, you can also specify an EnvironmentFile that points to a file containing KEY=value lines.
Logging for Custom Services
Systemd integrates with its logging system so that all output from your service is captured. By default, anything your program writes to standard output or standard error is sent to the journal.
You can view recent logs for the service with:
journalctl -u hello.service
Use -f to follow logs in real time.
If your program uses its own logging mechanism, you can still see messages through its normal log files. For many custom services, letting systemd capture the output is convenient, because you can keep the unit file simple and avoid setting up separate log files.
You can fine tune behavior using directives such as StandardOutput and StandardError if you need to redirect output, but typical custom services work well with the defaults.
Controlling User Services
Systemd can also manage services on a per user basis. These services run under that user’s session and do not require root privileges to define or manage.
To create a user service, place a unit file in ~/.config/systemd/user. For example:
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/notify.serviceUnit content could be:
[Unit]
Description=Desktop notification loop
[Service]
ExecStart=/home/alex/bin/notify_loop.sh
Restart=on-failure
[Install]
WantedBy=default.targetThen reload the user unit files:
systemctl --user daemon-reloadStart it with:
systemctl --user start notify.serviceEnable it so that it starts with the user session:
systemctl --user enable notify.serviceDepending on your distribution, you may need user level lingering enabled if you want user services to continue running when you are not logged in. That configuration is part of broader systemd and login management topics.
Adding Dependencies and Ordering
Sometimes your custom service must start after another service or after a resource becomes available. In unit files this is done by setting relationships with other units in the [Unit] section.
For example, suppose your service depends on the network being up. You might use:
[Unit]
Description=My networked app
After=network-online.target
Wants=network-online.target
After specifies the order. Wants specifies a weaker form of dependency that tries to start the specified units but does not fail hard if they do not start successfully.
You can also depend directly on other services, which is useful if your service expects a database daemon or other backend to already be running.
These dependency mechanisms are central to systemd and allow you to integrate custom services cleanly into the boot sequence without manual start scripts.
Overriding Existing Units with Drop-In Files
Sometimes you do not need to create a completely new service but want to customize a service shipped by your distribution. Instead of editing the original unit file, you can create drop in configuration.
The basic idea is that you place small configuration snippets in a directory named like name.service.d under /etc/systemd/system. For example, to modify nginx.service you can do:
sudo systemctl edit nginx.service
This command opens an editor and creates a drop in file, for example /etc/systemd/system/nginx.service.d/override.conf. Inside you might write:
[Service]
Restart=always
RestartSec=10When you save and exit, systemd will use the original packaged unit file combined with your override. This is useful if you later want to remove your customization without touching package owned files. The same drop in technique can be used for your own custom services if you want to maintain base and local variants separately.
After adding or editing drop ins you should run systemctl daemon-reload so systemd applies the new configuration.
Testing and Debugging Custom Services
When you develop a new service, it is wise to test it through systemd instead of running the command manually. Many issues arise from incorrect paths, missing permissions, or environment differences.
A practical approach is:
First, run your program directly in a terminal to confirm it behaves as expected in the foreground and logs errors clearly.
Next, write a minimal unit file with only Description, ExecStart, and possibly User and Group. Avoid complex settings at first.
Run systemctl daemon-reload, then systemctl start your.service.
Check systemctl status your.service to see if it is running, its exit code, and its recent log messages.
Use journalctl -u your.service to view full logs. If the service fails to start, the logs usually contain specific error messages such as permission denied, command not found, or missing files.
If the service loops due to Restart settings, you can temporarily comment out Restart or set Restart=no while debugging.
Important rule: After every change to a unit file or its drop in configuration, you must run systemctl daemon-reload before starting or restarting the service.
By iterating in small steps and inspecting status and logs, you can quickly converge to a fully working custom service definition.
Removing Custom Services
If you no longer need a custom service, you should disable and stop it, then remove its unit file.
First disable automatic starting:
sudo systemctl disable hello.serviceThen stop it if it is running:
sudo systemctl stop hello.serviceNext remove the unit file:
sudo rm /etc/systemd/system/hello.serviceInform systemd of the removal:
sudo systemctl daemon-reloadYou can verify that the service is gone by checking:
systemctl status hello.serviceIt should now show as not found or loaded inactive if some residual cache remains until the next reload.
For user services, you perform similar steps without sudo and remove the unit file under ~/.config/systemd/user.
Summary
Creating custom services with systemd allows you to integrate your own applications and scripts into the same management framework that handles core system daemons. By writing a small unit file, placing it in the correct directory, reloading systemd, and using familiar systemctl and journalctl commands, you gain automatic startup, controlled shutdown, logging, and restart behavior for your software. With a solid understanding of ExecStart, service Type, Restart policies, and the [Install] configuration, you can design reliable and maintainable services that fit cleanly into the overall system.