Set User Service Environment with systemd-environment-d-generator

systemd-environment-d-generator gets one extra variable to the services your user manager starts, without touching a login shell or a system file.

This guide sets a variable, verifies it over D-Bus, and removes it cleanly if it turns out wrong. Give it ten minutes. You need a Linux system on systemd 255 or nearby, with an active user manager. None of this needs root, because it only touches your own ~/.config directory.

1. Check the installed generator and manager

The generator is not something you run day to day; systemd runs it for you. On this machine, the installed package is systemd 255.4-1ubuntu8.17 and the user generator lives at /usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator. Confirm both the executable and the user manager before changing anything:

$ systemd --version
systemd 255 (255.4-1ubuntu8.17)
$ command -v systemctl
/usr/bin/systemctl
$ systemctl --user show-environment | sed -n '1,12p'
HOME=/home/alice
LANG=en_US.UTF-8
LOGNAME=alice

Your version, home directory and existing variables will all differ from this example. If systemctl --user says it cannot connect to a bus, fix the user session first. Do not reach for sudo systemctl --user: root has its own, entirely separate user manager.

Checkpoint: you have a live user manager, and you know this feature affects services it starts, not every process in every login shell you have open.

2. Create one environment file

Make the user configuration directory and a numbered file. This example adds a directory to PATH and defines a value a user service can read:

$ mkdir -p "$HOME/.config/environment.d"
$ umask 077
$ printf '%s\n' \
    'PROJECT_ROOT=/home/alice/projects/example' \
    'PATH=/home/alice/.local/bin:$PATH' \
    > "$HOME/.config/environment.d/60-project.conf"
$ sed -n '1,10p' "$HOME/.config/environment.d/60-project.conf"
PROJECT_ROOT=/home/alice/projects/example
PATH=/home/alice/.local/bin:$PATH

Replace /home/alice and /home/alice/projects/example with real paths of your own. The file is plain KEY=VALUE assignments; blank lines and lines starting with # are ignored. The right-hand side can reference an earlier variable with $NAME or ${NAME}, but this is not shell syntax: no command substitution, no shell quoting rules, no arbitrary operators.

The two-digit prefix is just an ordering aid, since files are sorted by name across all the configuration directories. A later filename wins if it sets the same variable again. Your own overrides live in ~/.config/environment.d; vendor files belong under /usr/lib/environment.d, and administrator overrides under /etc/environment.d.

3. Reload the user manager

Ask the user manager to reload its configuration:

$ systemctl --user daemon-reload

This reruns the environment generators. It is not a service restart, but any service started after this point gets the new environment; anything already running keeps whatever it started with. Only restart a particular service once you have checked that doing so is actually safe:

$ systemctl --user restart example.service

That restart is service-disrupting. Use a real unit name, and skip this command entirely if you only need the value for services that have not started yet. Do not restart something important purely to make a shell show you a variable.

Checkpoint: the reload finished without an error. If it failed, look for a misspelled variable name, a bad assignment, or a path copied literally from this example.

4. Verify the exported values

Read back what the user manager is actually exporting:

$ systemctl --user show-environment | grep -E '^(PATH|PROJECT_ROOT)='
PATH=/home/alice/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PROJECT_ROOT=/home/alice/projects/example

Order is not significant, and your original PATH will differ. What matters is that PROJECT_ROOT has the value you meant and the new directory appears ahead of the old ones. A unit started by this manager inherits these; a terminal you already had open, usually does not.

For a more direct check, run the generator by hand:

$ /usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator | sed -n '1,8p'
QT_ACCESSIBILITY=1
PATH=/home/alice/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PROJECT_ROOT=/home/alice/projects/example

That output is a diagnostic view, not a substitute for daemon-reload. The program takes no arguments, and some values, accessibility settings among them, are host-specific, so do not expect an identical list on your machine.

5. Undo the change safely

Keep the file until you have finished verifying. To remove it, delete only the file you created and reload:

$ rm "$HOME/.config/environment.d/60-project.conf"
$ systemctl --user daemon-reload
$ systemctl --user show-environment | grep -E '^(PATH|PROJECT_ROOT)='

Recovery: do not run that rm if the file already existed before you started. Restore its previous contents from backup or version control instead, then reload. Removing a vendor file is a different operation: mask its exact filename with a symlink to /dev/null under /etc/environment.d, and remove the mask to bring the vendor file back. Plan and record that one; it is an administrator-level change.

Know the boundary

These settings reach services started by the systemd user instance, including anything that launches a user shell inside some graphical sessions. They do not retroactively touch the manager itself, your current shell, unrelated system services, or every SSH login you have open. Anything started outside the user manager gets its environment from somewhere else entirely.

A later file can override an earlier one, and a same-named file in a higher-priority directory replaces a lower one completely. When debugging, list ~/.config/environment.d, /etc/environment.d, /run/environment.d, /usr/local/lib/environment.d and /usr/lib/environment.d, and compare filenames across all five. Keep secrets out of these files: the whole point is that the values get exported to multiple user services.

Done means