Think editing unit files is the only way to pass config to services?
Think again.
systemd gives two straightforward ways to set environment variables for services: Environment= inside the unit and EnvironmentFile= pointing to external KEY=value files.
Use Environment= for a couple of static flags, EnvironmentFile= for longer lists, shared config, or secrets you protect with file permissions.
This post walks through the syntax, precedence (drop-ins), runtime expansion in ExecStart, and common gotchas so you can pick the right method and skip one-hour debugging sessions.
Core Methods for Managing Environment Variables in systemd Services

systemd gives you two main ways to set environment variables in service units: Environment= and EnvironmentFile=. The Environment= directive goes inside your [Service] section and lets you define variables right there in the unit file, next to your ExecStart command. You can pack multiple variables onto one Environment= line (space them out) or spread them across several lines. Works well when you’ve got a few static values that don’t change much.
EnvironmentFile= works differently. Instead of hardcoding everything in the unit file, you point it at an external file full of simple KEY=value lines. systemd reads that file when the service starts and loads all the variables. You can use multiple EnvironmentFile= directives, and systemd processes them in order. Put a dash in front (EnvironmentFile=-/path/to/file) and systemd won’t complain if the file’s missing, which is useful for optional config.
Environment= uses quoted strings or space-delimited lists. EnvironmentFile expects plain KEY=value lines. Environment= embeds values directly in the unit file. EnvironmentFile= keeps them separate. Values from Environment= show up in systemctl show output, but EnvironmentFile content doesn’t get published that way. Environment= can hold several variables per line or across multiple lines. EnvironmentFile loads an entire file of definitions. With EnvironmentFile=, a leading dash ignores missing files. Environment= always has to parse correctly. Both need systemctl daemon-reload and systemctl restart <service> after changes.
Go with inline Environment= when you’re managing two or three simple flags that won’t change often and don’t need strict access control. Grab EnvironmentFile= when you’ve got a longer list, when you want to share config across multiple units, or when you need to manage secrets with file permissions. External files also keep your unit definitions tidy and make it easier to update config without touching the service definition.
Creating systemd Service Environment Variables in Unit Files

Defining variables directly in a unit file means adding Environment= lines to the [Service] section. Simplest form is Environment=KEY=value, but when your value has spaces or special characters, wrap it in quotes: Environment="GREETING=hello world". You can escape spaces with a backslash (Environment=GREETING=hello\ world), though quotes are clearer.
systemd lets you define multiple variables on one line by separating them with spaces: Environment="VAR1=value1" "VAR2=value2". Each variable gets quoted independently. Or just stack multiple Environment= lines in the same [Service] block. Both work the same. Pick whichever keeps your unit file readable.
Inline Syntax Variants
| Use Case | Example | Notes |
|---|---|---|
| Single variable, no spaces | Environment=MODE=prod | No quotes needed for simple alphanumeric values |
| Value with spaces | Environment=”GREETING=hello world” | Quotes required; backslash escape also works |
| Multiple variables, one line | Environment=”API_KEY=abc123″ “MODE=dev” | Each variable in its own quoted block |
| Multiple Environment lines | Environment=VAR1=a Environment=VAR2=b |
Stacking lines is functionally identical |
Once variables are defined, reference them in ExecStart or other directives using $VAR or ${VAR} syntax. systemd does variable substitution before launching the process. Keep in mind that ExecStart doesn’t run under a shell by default, so if you need shell features like pipes or redirection, wrap your command in /bin/sh -c "your command here". When you do that, make sure your variables are defined before the shell tries to expand them, or quote properly to avoid weird substitution timing.
Using EnvironmentFile to Manage Larger Sets of systemd Variables

EnvironmentFile= points to a plain text file where each line follows VarName=VarValue format. Unlike shell scripts, you don’t need quotes around values unless you’re doing something unusual. A file at /etc/myservice/env might have lines like API_KEY=abc123 and LOG_LEVEL=debug, one per line. When the service starts, systemd reads the file and loads every variable into the runtime environment.
You can specify EnvironmentFile= multiple times in the same unit, and systemd processes each file in order. If a variable appears in more than one file, the last definition wins. Adding a leading dash (EnvironmentFile=-/etc/myservice/optional.env) tells systemd to skip the file silently if it doesn’t exist, useful when you have optional config layers or environment-specific overrides that might not always be present.
Store environment files in /etc/<service>/ or /etc/default/<service>, owned by root:root with mode 600 (readable only by root). EnvironmentFile contents don’t show up in systemctl show output the same way Environment= values do, making them slightly less exposed, but filesystem permissions are the real protection. Ensure the directory and file aren’t world-readable. Use chmod 600 /etc/myservice/env and chown root:root. On distributions with SELinux, verify that the file context allows systemd to read it. Use restorecon if needed. External files are easier to rotate or update from configuration management tools without touching unit file definitions.
Use EnvironmentFile when you have a growing list of configuration values, when you need to share the same environment across multiple services, or when you’re storing secrets that should be isolated from the unit file. It keeps unit definitions concise and makes it easier to maintain different environments (dev, staging, prod) by swapping out a single file. For truly sensitive credentials, consider a dedicated secret manager like Vault and use EnvironmentFile for non-secret config, or inject secrets at runtime via a script that fetches from your secret store and writes the file just before service start.
Overriding systemd Service Environment Variables with Drop-Ins

When you need to change a variable defined in a packaged unit file without editing the file itself, use a drop-in override. Run sudo systemctl edit <service> and systemd opens an editor for a new file in /etc/systemd/system/<service>.service.d/override.conf. Any directives you add in this file take precedence over the original unit, letting you override Environment= or EnvironmentFile= entries cleanly.
Precedence Rules
systemd merges configuration from several layers. Variables defined in vendor-provided units (typically in /usr/lib/systemd/system) are the baseline. Local units in /etc/systemd/system override vendor units. Drop-in files (.d directories under /etc/systemd/system) then override both. Transient runtime changes made with systemctl set-environment can affect manager-level environment, though those are less common for per-service variables.
Vendor unit lives at /usr/lib/systemd/system/<service>.service with default definitions from the package. Local unit at /etc/systemd/system/<service>.service is your custom copy or symlink, overrides vendor. Drop-in overrides at /etc/systemd/system/<service>.service.d/*.conf have highest priority for per-service config. Transient overrides from systemctl set-environment affect the systemd manager environment, not individual service units unless inherited.
Typical use cases for drop-ins include changing a production API endpoint without forking the vendor unit, adding a secret token from an EnvironmentFile that wasn’t included upstream, or toggling debug flags in a staging environment. After saving your override, always run sudo systemctl daemon-reload to tell systemd to re-parse the units, then sudo systemctl restart <service> to apply the new environment. Drop-ins are also version-control friendly. You can track your overrides separately from the base unit and apply them with configuration management tools.
Referencing and Expanding Environment Variables in systemd Services

systemd expands variables inside Exec directives using $VAR or ${VAR} syntax. When you write ExecStart=/usr/bin/mybinary --flag $MODE, systemd substitutes the value of MODE before launching the process. This happens at service startup, so the running process sees the expanded value. Variables defined via Environment= or EnvironmentFile= are inherited by the process and any children it forks, making them available throughout the service’s lifetime.
Because ExecStart isn’t executed under a shell, shell features like globs, pipes, and command substitution won’t work unless you explicitly invoke a shell. If you need that, wrap your command: ExecStart=/bin/sh -c "echo $GREETING >> /var/log/myservice.log". Be careful with quoting when you do this. If GREETING contains spaces or special characters, make sure your shell command handles it correctly, or use ${GREETING} and quote inside the shell command string.
Writing ExecStart=echo $VAR > /tmp/out fails because > is shell syntax. Wrap with /bin/sh -c. If $VAR contains spaces and you don’t quote it in your shell command, it splits into multiple arguments. Referencing $MISSING_VAR when it’s not defined results in an empty string. Check definitions carefully. systemd expands variables before execution, so runtime changes to environment (like exports inside a script) won’t retroactively affect ExecStart.
When in doubt, test your ExecStart command manually on the command line, then translate it into the unit file. If it works in a shell, wrap it with /bin/sh -c and quote appropriately. If it’s a simple binary invocation with arguments, keep it direct and let systemd handle the variable substitution.
Debugging systemd Service Environment Variables

When variables don’t appear or services fail to start, check the journal first. journalctl -u <service> -b shows you boot logs and startup messages, including any errors from missing files or malformed environment entries. If the service started but variables seem wrong, use systemctl show <service> -p Environment to see what systemd actually passed to the service. This output reflects the merged configuration from all unit files and drop-ins.
For a live process, inspect its environment directly via /proc/<PID>/environ. Find the PID with systemctl status <service> or pidof <binary>, then run sudo tr '\0' '\n' < /proc/<PID>/environ to dump the environment as readable lines. This shows exactly what the running process sees, which is the ground truth if you’re troubleshooting missing or incorrect values.
Edited a unit or drop-in but didn’t run systemctl daemon-reload? systemd’s still using the old cached version. Unescaped spaces or missing quotes around values can cause parse failures. Check journalctl for syntax errors. If systemd can’t read the EnvironmentFile, the service may fail to start or silently skip variables. Verify path, ownership, and permissions. Typo in the EnvironmentFile path or the file moved? Use the leading dash to make it optional or fix the path. Restarted the service but still see old values in /proc/<PID>/environ? Confirm the service actually restarted and you’re checking the new PID. On systems with SELinux enforcing, context mismatches can prevent systemd from reading environment files. Check ausearch or journalctl for denials and run restorecon.
After making changes, always follow this sequence: edit the file, systemctl daemon-reload, systemctl restart <service>, then check logs and verify the new values are live. If the service doesn’t restart cleanly, systemctl status <service> shows the most recent error, and journalctl -xe gives you the full context.
Differences Between system and User Services for Environment Variables

System services live in /etc/systemd/system or /usr/lib/systemd/system and run with privileges defined by the User= directive (or as root if not specified). You manage them with systemctl commands, and their environment variables are set via Environment= or EnvironmentFile= as described. User services are stored in ~/.config/systemd/user and managed with systemctl --user. Each logged-in user has their own systemd instance, and user services inherit the user’s session environment plus any variables defined in user unit files.
When you set a variable in a system unit, it applies to that specific service and is isolated from user sessions. When you set a variable in a user unit, it applies only to services running under that user’s systemd instance. Manager-level environment commands like systemctl set-environment VAR=value affect the systemd manager’s own environment. For system services, this is the system-wide manager. For user services, run systemctl --user set-environment to modify the user manager’s environment. These transient changes don’t persist across reboots unless you add them to a persistent config.
System units are in /etc/systemd/system and /usr/lib/systemd/system. User units are in ~/.config/systemd/user and /etc/systemd/user. System unit files are typically owned by root. User unit files are owned by the user and only affect that user’s services. User services inherit variables from the user’s login session. System services do not, unless explicitly set in the unit or EnvironmentFile.
If you’re running a service as a regular user (personal web server or a development database, for example), put the unit in ~/.config/systemd/user and manage it with systemctl --user. If it’s a system-wide daemon (web server for all users, monitoring agent), use /etc/systemd/system and systemctl without the --user flag. The environment mechanisms are the same, but the scope and permissions differ.
Security Best Practices for systemd Service Environment Variables

Unit files in /etc/systemd/system are readable by any user who can run systemctl cat or inspect the filesystem, so never store plaintext passwords, API tokens, or private keys directly in Environment= lines. When you must include secrets, use EnvironmentFile= and lock down the file permissions: chmod 600 /etc/myservice/secrets.env and chown root:root. This prevents non-root users from reading the file, though root and the service itself will still have access.
For production systems, prefer external secret management tools like HashiCorp Vault, AWS Secrets Manager, or systemd’s own integration points. You can write a small ExecStartPre script that fetches secrets from Vault and writes them to a temporary EnvironmentFile just before the main process starts, then cleans up on ExecStopPost. This keeps secrets out of static files and logs. If you need interactive secret entry, systemd-ask-password can prompt at boot, but that’s not practical for headless servers.
Ensure EnvironmentFile entries are mode 600, owned by root, and located in a directory with restricted access. Use Vault, cloud secret managers, or encrypted storage instead of plain files. Fetch secrets at runtime. Don’t commit secrets to version control in unit files. Treat them like you would SSH keys or database passwords. Design your EnvironmentFile or fetch script to support regular rotation without manual edits to the unit. Use SELinux and AppArmor to further restrict which processes can read environment files.
Always audit who has access to /etc/systemd/system and any directories holding EnvironmentFile sources. If your configuration management pushes environment files, verify that the deployment pipeline encrypts secrets in transit and at rest, and that only authorized systems can decrypt and write them. Security for systemd environment variables is all about layering filesystem permissions, access control policies, and external secret tooling to keep sensitive data out of easily inspected config files.
Final Words
in the action, we ran through the two core ways to set variables — Environment= inline and EnvironmentFile= external — plus drop-ins, interpolation rules, debugging commands, and security checks.
Use inline for quick, simple values; use EnvironmentFile or drop-ins for maintainability, secrets, or multi-host setups. Don’t forget daemon-reload and restart, and check /proc/
With these patterns, systemd service environment variables become predictable and easier to manage. You’ve got this.
FAQ
Q: What are the primary ways to set environment variables in systemd services?
A: The primary ways to set environment variables in systemd services are Environment= inside the [Service] unit and EnvironmentFile= to load external KEY=value files; both require daemon-reload and service restart to apply.
Q: What is Environment= and how is it used?
A: The Environment= directive sets variables inline in a unit’s [Service] section; you can list multiple space-delimited VAR=VAL entries on a line, quote values with spaces, then reload and restart to apply.
Q: What is EnvironmentFile= and how does it work?
A: The EnvironmentFile= directive loads KEY=VALUE lines from external files; files use simple VAR=VAL syntax (no quotes needed), support multiple directives, and a leading dash suppresses missing-file errors.
Q: When should I use Environment= versus EnvironmentFile=?
A: Use Environment= for a few simple inline variables and quick checks; use EnvironmentFile= when you manage many variables, want versioned files, or store sensitive values with tighter permissions.
Q: How do I write inline variables and handle quoting or escaping?
A: You write inline variables as VAR=VALUE in the unit; quote values containing spaces or escape special chars, and use $VAR or ${VAR} for substitution because ExecStart runs without a shell.
Q: How does variable expansion work in ExecStart and when do I need a shell?
A: Variable expansion in ExecStart uses $VAR or ${VAR} and is not executed under a shell; wrap the command with /bin/sh -c if you need shell features like pipes, globbing, or complex expansions.
Q: How do drop-ins work and what is the precedence order?
A: Drop-ins created with systemctl edit override main unit values and live in /etc/systemd/system/
Q: How do I debug missing or incorrect environment variables for a service?
A: You debug missing variables by checking journalctl -u
Q: How do system and user services differ for environment variable handling?
A: System services use /etc or /usr system paths and need root privileges; user services live in ~/.config/systemd/user and run per-session; manager-level env can be set transiently with systemctl set-environment/unset-environment.
Q: What are security best practices for storing secrets in systemd environment variables?
A: Follow security best practices: avoid secrets in unit files, prefer secret managers, use EnvironmentFile owned by root with mode 600, rotate secrets regularly, and verify SELinux contexts and file permissions.
