How to Use Environment Variables in systemd Service Files for Parameterized Commands


13 views

Systemd provides several robust methods for working with environment variables in service files, offering more flexibility than traditional init scripts. The main approaches include:

The simplest way is to use the Environment directive in the [Service] section:

[Service]
Environment=PARAM1=123
Environment=PARAM2=444
ExecStart=/usr/local/bin/daemon1 --param1=${PARAM1} --param2=${PARAM2}

For multiple variables, create an environment file (e.g., /etc/default/daemon1):

# /etc/default/daemon1
PARAM1=123
PARAM2=444

Then reference it in your service file:

[Service]
EnvironmentFile=/etc/default/daemon1
ExecStart=/usr/local/bin/daemon1 ${PARAM1} ${PARAM2}

Systemd supports parameterized services using the @ syntax:

[Unit]
Description=Daemon instance %i

[Service]
Environment=INSTANCE=%i
ExecStart=/usr/local/bin/daemon1 --instance=${INSTANCE}

Start with: systemctl start daemon1@instance1.service

For your specific wget example, here's the correct implementation:

[Service]
Environment=PARAM1=123 PARAM2=444
ExecStartPre=/bin/sh -c '/usr/bin/wget -O - --post-data="key1=${PARAM1}&key2=${PARAM2}" http://192.168.1.2/log.php'
ExecStopPost=/bin/sh -c '/usr/bin/wget -O - --post-data="key1=${PARAM1}" http://192.168.1.2/log.php'
  • Variables won't expand directly in Exec* directives - wrap them in /bin/sh -c
  • Use systemctl show --property=Environment service-name to verify variables
  • Remember to systemctl daemon-reload after changes
  1. Store sensitive variables in /etc/systemd/system/.env with restricted permissions
  2. Use EnvironmentFile=-/path/to/file (with dash) to make the file optional
  3. Prefix variable names with your application name (e.g., MYAPP_PARAM1)

When transitioning from traditional init scripts to systemd services, many developers encounter difficulties with parameterization. The example service file attempts to:

  • Declare environment variables (PARAM1, PARAM2)
  • Reference these variables in ExecStartPre and ExecStopPost commands
  • Maintain script-like flexibility within a declarative configuration

Systemd uses a different syntax for environment variables than traditional init scripts. Here's the corrected version:

[Unit]
Description=my daemon
After=network.target

[Service]
Environment=PARAM1=123
Environment=PARAM2=444
ExecStart=/usr/local/bin/daemon1
PIDFile=/var/run/daemon1.pid
ExecStartPre=-/usr/bin/bash -c '/usr/bin/wget -O - --post-data="key1=${PARAM1}&key2=${PARAM2}" http://192.168.1.2/log.php'
ExecStopPost=-/usr/bin/bash -c '/usr/bin/wget -O - --post-data="key1=${PARAM1}" http://192.168.1.2/log.php'
Type=simple

[Install]
WantedBy=multi-user.target

Three critical techniques make this work:

# 1. Proper environment declaration
Environment=VARNAME=value

# 2. Shell invocation for variable expansion
ExecStartPre=-/usr/bin/bash -c 'command ${VARNAME}'

# 3. Escaping special characters
--post-data="key1=${PARAM1}&key2=${PARAM2}"

For more complex scenarios, consider these approaches:

# Using environment files
EnvironmentFile=/etc/default/my-daemon

# Multiple variables in one line
Environment="PARAM1=123" "PARAM2=444"

# Dynamic values with shell commands
ExecStartPre=-/usr/bin/bash -c 'export TIMESTAMP=$(date +%s); wget --post-data="ts=${TIMESTAMP}" example.com'

When variables don't expand as expected:

  1. Check service status: systemctl status my-daemon
  2. Inspect environment: systemctl show my-daemon --property=Environment
  3. Test command manually: sudo -E bash -c 'echo ${PARAM1}'
[Unit]
Description=Python Web Service
After=network.target

[Service]
Environment=PORT=8080
Environment=HOST=0.0.0.0
ExecStart=/usr/bin/python3 -m http.server ${PORT} --bind ${HOST}
ExecStartPre=/usr/bin/bash -c 'echo "Starting on ${HOST}:${PORT}" > /var/log/web-start.log'

[Install]
WantedBy=multi-user.target