Set a Reliable User-Service Environment with environment.d

You exported a variable in .bashrc and your systemd user service still cannot see it, which is exactly what environment.d is for. You will set a variable for services under your systemd user instance, extend an existing value safely, and verify the result with systemctl --user show-environment. The examples use systemd 255.4-1ubuntu8.17. Allow about fifteen minutes, including a reload and a small test.

You need a normal user shell and a running systemd user manager. The file belongs to your account, so the main workflow does not need sudo. This mechanism changes the environment passed to user services. It does not rewrite the environment of the already-running user manager itself, and it does not change unrelated login shells.

1. Check the user manager and current environment

Start with read-only checks. They show whether the user instance is reachable and what it exports before you change anything:

$ systemctl --user is-system-running
running
$ systemctl --user show-environment | grep '^PATH='
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Your status may be degraded while the manager is still usable. If the command cannot connect to the user bus, fix that session problem first. An ordinary login shell's printenv output is not proof of what the user manager exports.

Checkpoint: write down the variable you want to add or change, and decide whether it is for every user service or one unit. A unit-specific Environment= setting in a service override may be a better boundary when the value is sensitive or only one service needs it.

2. Create a numbered user configuration file

Create the per-user directory and a file with a two-digit prefix. Both are ordinary user-owned state:

$ install -d -m 0755 "$HOME/.config/environment.d"
$ nano "$HOME/.config/environment.d/60-project.conf"

Use whichever editor you like. Put one assignment on each line. This example adds a tool directory in front of the existing PATH:

PROJECT_HOME=/home/alice/projects/example
PATH=${PROJECT_HOME}/bin:$PATH

Use your real home directory and project path instead of copying /home/alice blindly. The file format has a few rules:

For a value with a fallback, use the documented forms:

CACHE_ROOT=${CACHE_ROOT:-/var/cache/example}
OPTIONAL_FLAG=${OPTIONAL_FLAG:+enabled}

The first form uses the fallback when the earlier value is empty. The second produces enabled only when the earlier value would be non-empty.

Warning: keep secrets out of a broadly inherited environment where you can. Environment variables can be exposed through service diagnostics and process inspection.

3. Work out which file wins

systemd reads .conf files from the user directory, then the administrator and runtime locations, then the local and vendor locations. In precedence order, the directories are:

~/.config/environment.d
/etc/environment.d
/run/environment.d
/usr/local/lib/environment.d
/usr/lib/environment.d

Files are sorted together by filename, not separately within each directory. A file in a higher-priority directory replaces a same-named file from a lower-priority one. For different names, the lexicographically later assignment wins. So 60-project.conf can extend a vendor value, while 90-project-local.conf can override an earlier assignment.

A file in ~/.config/environment.d does not automatically win every conflict. A vendor file with a later name can still win for an individual variable. When the result surprises you, list the installed files:

$ find /usr/lib/environment.d /usr/local/lib/environment.d /run/environment.d /etc/environment.d "$HOME/.config/environment.d" \
    -maxdepth 1 -type f -name '*.conf' -print 2>/dev/null | sort

4. Reload the user manager

Ask systemd to rerun its environment generators after you save the file:

$ systemctl --user daemon-reload

This is an ordinary user-manager operation and normally needs no elevated privileges. The generator runs at manager startup and at configuration reload. Services started afterwards get the refreshed environment. A service that is already running keeps the environment it received when it started.

Warning: do not restart a production service just to make a variable visible until you have checked the value and confirmed a restart is acceptable. If one is required, treat it as a service-disrupting action and use that unit's normal maintenance procedure.

5. Verify the exported value

Query the user manager, not just the shell where you edited the file:

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

Line order can differ. Check the value itself, and confirm the expected directory appears once, in the intended position. If the variable is missing, check the extension, file path and variable name, then run systemctl --user daemon-reload again.

To see the generator's diagnostic trace without changing configuration, run:

$ SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator
... reading environment.d files ...

The installed generator prints the calculated assignments to standard output. Debug wording and values depend on the machine, so use the final assignment as your evidence.

6. Test a new service process

show-environment confirms what the manager exports. To check that a service inherits it, run a harmless transient user unit, if transient units are available:

$ systemd-run --user --wait --pipe /usr/bin/sh -c 'printf "PROJECT_HOME=%s\n" "$PROJECT_HOME"'
PROJECT_HOME=/home/alice/projects/example

This starts a short-lived process and leaves no persistent unit file. If systemd-run is unavailable or policy blocks transient units, inspect an existing non-critical user service through its normal logs instead.

Tip: do not use a production service as a test target just to prove a path variable.

7. Undo the change

To remove only your setting, delete the assignment from the file or move the file out of the directory. Moving is reversible:

$ mv "$HOME/.config/environment.d/60-project.conf" \
    "$HOME/.config/environment.d/60-project.conf.disabled"
$ systemctl --user daemon-reload
$ systemctl --user show-environment | grep '^PROJECT_HOME=' || echo 'PROJECT_HOME is not exported'

The disabled file no longer has the required .conf extension, so the generator ignores it.

Recovery: keep the disabled file until you have confirmed that dependent user services no longer need the value. Existing services may still hold the old value until they are restarted, so check their lifecycle before you rely on the removal.

Done means