Three years ago when I wrote The Joy of Upstart, that was the easiest way to turn your scripts into daemons. Today the future belongs firmly to systemd, so let’s revisit the Upstart post, and update it for systemd.
systemd is here, it is the default on most major distributions including Ubuntu from 15.04, Fedora from 15, and Red Hat from v7, so you can probably use it now. The main exception is Ubuntu LTS, where we need to wait a few more months for 16.04.
Take this python script:
import time while 1: print("I'm a daemon!") time.sleep(1)
We’re going to turn it into a daemon in just two lines. Create
[Service] ExecStart=/usr/bin/python -u /home/myuser/ez_daemon.py
And that’s it!
systemctl start ez_daemon
journalctl -f -u ez_daemon. Stdout goes to the journal by default (more below).
systemctl status ez_daemon. The status page is actually useful.
systemctl stop ez_daemon
A systemd file (such as
/etc/systemd/system/ez_daemon.service) is called a unit. Paths in units must be absolute, hence
/usr/bin/python. systemd caches units. When you change one you must
systemctl daemon-reload to use the latest version. systemd will remind you.
A major difference with Upstart is that stdout and stderr are now buffered. Upstart went the extra mile by wrapping our script with a pseudo-tty to prevent the buffering. systemd uses a pipe, which means by default there is a 4k buffer on stdout and stderr. You won’t see anything in
journalctl until that buffer fills. We pass
-u to python to prevent this buffering, or we could have called
sys.stdout.flush() after each print. Note also that you can’t have end-of-line comments, a comment must be on a line by itself.
Here’s a more complete example:
[Unit] Description="Example daemon" # Don't start until the network is available Requires=network-online.target After=network-online.target [Service] ExecStart=/usr/bin/python -u /home/myuser/bin/ez_daemon.py Restart=on-failure User=www-data Group=www-data Environment=\ PYTHONPATH=/srv/example/src/example/ \ ANSWER=42 \ "GREET=Hello systemd" # /usr, /boot and /etc are read-only ProtectSystem=full # even safer: ProtectSystem=strict # $HOME is read only .. ProtectHome=read-only # .. except /home/myuser/logs ReadWriteDirectories=/home/myuser/logs # /tmp is isolated from all other processes PrivateTmp=true # Minimal /dev, no physical device access PrivateDevices=true # Don't allow process to raise privileges (e.g. disable suid) NoNewPrivileges=true # No network access PrivateNetwork=true [Install] WantedBy=multi-user.target
A particularly exciting part of systemd is the security features enabled by cgroups. The example script includes most of them, which you’ll want to remove as necessary. For example
PrivateNetwork wouldn’t be applicable for nginx. Here are full details on systemd security. UPDATE 2018: systemd now has the even safer
ProtectSystem=strict and dynamic users.
syslog. As a convenience Ubuntu still runs syslog, messages are available in
/var/log/syslog. Fedora no longer runs syslog by default, so
/var/log/messages is empty. Instead you use
journalctl, which has greatly improving filtering capabilities. Examples in Linux Voice.
Another exciting feature of systemd is socket activation, for example systemd can open port 80 and hand it to your process. You no longer need to start as root, open the port, then drop privileges. This USENIX Login has examples, and is probably the best place to continue reading after this post.
Ubuntu maintains tips for converting Upstart scripts to systemd.
systemd can also replace cron, run containers, and much more. It’s our future, and it’s going to be amazing.