Run any Executable as Systemd Service in Linux

Abhinand
8 min readNov 7, 2020

--

Ever wondered how to create a Linux service to manage your executables?

Source

This article was originally written in my personal blog abhinand5.github.io

Sometimes you might build an application and by nature, it might be important to keep it running forever. For example, a server program where downtimes are simply unacceptable or an agent that has to always run in the background without requiring any manual intervention to start, stop or restart. There are multiple ways of doing this — keeping it running forever. But in this article, I’ll cover a standard Linux way of doing this — this can also be used in production. Of course, I am not talking about (almost) self-managed environments like docker, AWS Lambda, Heroku etc… This is going to be only about plain old Linux servers/desktops.

What is systemd?

Systemd is a system manager and initialization tool that has become widely popular in recent years. Systemd is also the default init system in most of the well known Linux distributions. So knowing how to create a service in systemd might not be all that bad after all. Some of the popular distros that run on systemd by default include,

  • Arch Linux
  • Debian Jesse or newer
  • CentOS 7 / RHEL 7 or newer
  • Ubuntu 16.04 or newer
  • Fedora 15 or newer

The main purpose of an init system like systemd is to initialize the components that need attention after the Linux kernel itself boots up. But it also offers a mechanism to manage system services and daemons as long as the system is running, in form of systemctl which is what we are interested in. However, I am not going to delve too much into the details of how systemd works, that in itself needs several articles.

There are many services that the OS runs at any moment, for example, the keyboard setup service which enables the use of keyboard with your preferred layout in the console. There are several of these services which always run in the background to bring you a more seamless experience.

You can see a list of all services using the command below

$ systemctl list-units --type=service
systemctl list-units example

As you can see from the image above, Postgres or any other database management system also runs as a service in Linux. You can check the status of any service like this,

$ systemctl status postgresql

You may have noticed that the system services end with .service extension, these are nothing but files that are used to define a service in systemd . systemctl is smart enough to understand that you’re looking for a service and correctly display the status, however, you can add in the .service extension as a good practice.

You can view the contents of the service file like this,

$ systemctl cat postgresql

This file is formally known as a Unit file which is used to describe a system service. We will talk about this in the next section.

Creating our own systemd service

The magical file that lets us create a systemd service is called a Unit file. I say magical because it is much easier to write and setup. For a functioning service, this file must be present only in the following directories

$ man systemd.unit
Example from systemd manual

These are the directories from which systemd will load the information from. The one that we are interested in mainly is /etc/systemd/system where the admin created system units can reside.

For this article, I’ll use a simple echo server written in Python which I binarized with pyinstaller to show you how to run an executable as a service. Alternatively, we will also see how to directly run the python file from a shell script and turning that into systemd service.

Here is the simple python server I’m using,

For now, you can start creating a unit file here and then finally when it is done move it to /etc/systemd/system the directory.

Creating a unit file with the name of your service

$ touch echo-server.service

You can edit it with any editor of your choice, I’ll be using the inbuiltnano editor to keep it simple.

We can start by defining the description of the service we are creating.

[Unit]
Description=Service that keeps running the echo-server from startup.

Next, we should define when the service actually should start (run level)

[Install]
WantedBy=multi-user.target

multi-user.target normally defines a system state where all network services are started up and the system will accept logins, but a local GUI is not started. This is the typical default system state for server systems, which might be rack-mounted headless systems in a remote server room. This is just another way of telling the service should start at run-levels 3,4 and 5.

If your service needs an internet connection to even start, you should wait till the basic network functionalities are established. This can be achieved by including the following line under the[Unit] section of the file.

After=network.target

Now it is time to define the service itself, this is done under the [Service] section of the unit file.

[Service]
Type=simple
ExecStart=/home/abhi/Dev/echo-server/server
WorkingDirectory=/home/abhi/Dev/echo-server
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n

Now let's see what each line in this section is doing.

Type=simple

This means we are specifying how to run the service using ExecStart in the next line, where we specify the path to the binary executable file which systemd will use to start up the service. There are also other types of services like forking — which is used when the service forks a child process, exiting the parent process almost immediately. This tells systemd that the process is still running even though the parent exited. These are the most commonly used ones, for more types, you can verify the documentation of systemd.

WorkingDirectory=/home/abhi/Dev/echo-server

You can also mention the working directory of the service which is optional.

Restart=always
RestartSec=5

This means that the service will be restarted automatically in 5 seconds if the service fails or aborts at any time. This can be particularly useful for servers and agents running in the background.

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n

The above lines help in automatically redirecting the output from the service to a log file.

Running the Service and monitoring it

Now since the unit is created, we can move it to the /etc/systemd/system directory.

In the screenshot above I am moving the unit file to the right directory and verifying it by piping ls command in the systemd directory with the file name of the unit file we created.

Next, let us reload systemctl to make our new unit file visible.

$ sudo systemctl daemon-reload

Now enable our service using the command below,

$ sudo systemctl enable echo-server.service

If everything is set up correctly, you should get an output like this saying created symlink.

Now finally you can start your service using this command,

$ sudo systemctl start echo-server.service

This won’t print any output on the terminal.

Next, try to check the status of the service using this command,

$ sudo systemctl status echo-server.service

If everything works correctly, you’ll be able to see the service status as active and running. Congratulations! You’ve created your first Linux service.

If you want to see the logs of the service,

$ journalctl -f -u echo-server.service

You can now reboot to verify if the service is starting automatically.

As seen in this image, the system has booted up less than a minute ago and we can see that the service has started automatically just 39 seconds ago which is impressive.

Run a shell script as service (Alternative)

This method can be useful when you do not have a standalone binary executable for your application. First, create a shell script that invokes your program. In this case, we should write the python command that executes our program.

Create a shell script with the name of your choice.

$ touch start-echo-server.sh

Now edit the shell script and add the command that invokes your program

#!/bin/bashSCRIPT_PATH=/home/abhi/Dev/echo-server/server.py
PYTHON_PATH=/home/abhi/.local/bin/.virtualenvs/main/bin/python
$PYTHON_PATH $SCRIPT_PATHexit 1

You might know the script path, but you may not know the python path. This shell script will work even if you’re using a python virtual environment (venv). If you’re not using a venv, just execute the command below, otherwise activate the venv and then execute the command below, add the output as PYTHON_PATH in the shell script.

$ which python

Do not forget to give executable permission for the script, otherwise, this will not work.

$ chmod +x start-echo-server.sh

Now save this shell script and move it to /usr/sbin the directory. Why there? Because that is where we should put the general system-wide binaries that need admin privileges to run.

$ sudo mv start-echo-server.sh /usr/sbin
Example

Now you only need to change one line in the unit file — ExecStart

The rest of the procedure is exactly the same. If you have succeeded in creating the service you should get a status like this.

Removing the systemd service

Now let's see how to remove the service completely from the system.

  1. Stop the service if it is running
$ systemctl stop echo-server.service

2. Disable the service

$ systemctl disable echo-server.service

3. Delete the Unit file

$ sudo rm /etc/systemd/system/echo-server.service

4. Reload systemctl

$ systemctl daemon-reload

5. Delete the shell script (optional — if you’ve used a shell script)

$ sudo rm /usr/sbin/start-echo-server.sh

6. Clear out the units that have failed (Optional)

$ systemctl reset-failed

Conclusion

Being able to create these services can be really helpful in many cases, this is just one example where I’ve shown you how to do this but this method can be adopted for several problems where systemd services can do the job elegantly most of the times without involving any third-party tools.

Thanks a ton for reading all the way. If you like the article feel free to hit that clap icon which motivates me to write more good articles.

--

--

Abhinand
Abhinand

Responses (2)