systemd services fail with User= in service file

The problem you hit is that System V init scripts expect to run as root, that's part of the "specification" on how they work and they'll often have steps that need root to complete.

In your case, it was about running su <userid> -c ... to actually start running as the non-root user, but that part actually fails if you're already running under that user. System V init scripts will often use tools such as su or runas or similar to switch to a non-root user, but these tools are often not perfectly suited for that purpose (su is originally meant to run from an interactive shell and will integrate with PAM which doesn't make much sense here.)

Even worse, some System V init scripts will not deal with changing user and will end up unnecessarily running a daemon as root, since that's what feels more "natural" in a System V init script. This is, in my opinion, one of the worst issues of System V init scripts, they make it easy to do the wrong thing here, and hard to do the right one.

If you want to keep compatibility with a System V init script, you could just run them as root from systemd, since that's the "protocol" when invoking such scripts. In fact, if you wish to keep compatibility, you don't even need to ship a systemd unit, since systemd will be able to generate one by itself through systemd-sysv-generator. The generated unit will look a lot like the one you presented, except that it will be run by root instead.

If you do want to ship a systemd service unit (which I'd recommend that you do), then you should seriously consider shipping a unit that uses Type=simple, rather than forking.

The only pre-requisite for that is that you're able to start the daemon in foreground, which many daemons are able to do by passing an extra command-line flag or through some configuration. (It actually takes considerably less effort to do so, so if your daemon currently doesn't support that and you have control of the sources, consider adding or requesting that feature.)

At that point, all you need is to call your daemon in foreground from an ExecStart= directive. You don't need an ExecStop=, as long as your daemon properly terminates upon receiving a signal to kill it.

You don't need a pidfile anymore! Since systemd is launching the daemon process in foreground, it knows what the main PID of the daemon process is. This is huge, because pidfiles are often/usually implemented incorrectly (they're supposed to be created only once the daemon is ready to serve), so getting rid of that requirement is quite a big deal.

If you need to export specific environment variables before starting the main process, you can use systemd's Environment= or perhaps EnvironmentFile= to set those variables. (This will work fine, as long as the variables are set to fixed values rather than dynamically generated values.) If you need to execute steps before the daemon starts, you can use ExecStartPre=.

If you need more flexibility (for example, conditionally setting variables or running commands, or setting variables to dynamic values, etc.) then you should wrap the startup in a shell script (or Python, Perl, etc.) and call that script in ExecStart=. The script will set and export any variables needed, run any commands that need to be run, before executing the main daemon.

The important part when using a shell script to start a daemon is to use the exec command, in order to replace the shell with the daemon program. That means the shell will no longer be around, and the daemon will be running under the same PID that was in use by the shell, so systemd will still reliably know the main PID of the daemon. Of course, the daemon exec'd by the shell script should still run in foreground.

Using a Type=simple service allows you to configure User= in the systemd unit itself. Furthermore, you typically can apply more security measures through systemd configuration, that might have tripped a System V init script and prevented their usage. Also, using simple rather than forking makes this setup much more reliable, it's also more efficient on the system.

You can easily ship both a systemd service unit with Type=simple and a System V init script on your package. As long as they have the same name, systemd will prefer the native service unit (so the legacy code of systemd-sysv-generator will not trigger for the init script in that case.) That way you keep compatibility with non-Linux and other non-systemd setups, while at the same time you're able to make the most out of modern Linux systems with systemd.


The complication was the "su -c" command in the underlying script that started the daemon. System did not like that. But since the script has to work on other platforms as well, I modified it with a case statement for Linux. Since the systemctl is run under sudo anyway, it is not a problem. Now pid file can be written wherever I need it to be and the system seems happy.