147

When I ssh into a server, how can I pass an environment variable from the client to the server? This environment variable changes between different invocations of ssh so I don't want to overwrite $HOME/.ssh2/environment every time I do an ssh call. How can I do this?

6
  • 2
    You question needs to be a liiittle more specific. Jul 13, 2010 at 17:09
  • 5
    The question was clear enough to me. However, from the ssh man page, I don't see any way to do that other than setting the variable manually once you've logged in to the server, unless you modify ~/.ssh2/environment.
    – garyjohn
    Jul 13, 2010 at 17:23
  • Is it a different variable each time? Or a different value? Jul 13, 2010 at 17:52
  • 2
    Possible duplicate of how can I pass an environment variable through an ssh command?
    – Lætitia
    Dec 21, 2015 at 12:32
  • 2
    Other way round, as this is already more popular. It doesn't matter it's older.
    – kenorb
    Dec 23, 2015 at 10:10

11 Answers 11

162

Of course, you can set the environment variable inside the command, however you'll have to be careful about quoting: remember that your shell is going to parse your local command line, and then the remote shell will have a go on the string it receives.

If you want a variable to get the same value on the server that it has on the client, try the SendEnv option:

ssh -o SendEnv=MYVAR server.example.com mycommand

This requires support from the server, though. With OpenSSH, the variable name has to be authorized in /etc/sshd_config.

If the server only allows certain specific variable names, you can work around that; for example a common setup allows LC_* through, and you can do the following:

ssh -o SendEnv=LC_MYVAR server.example.com 'MYVAR=$LC_MYVAR; unset LC_MYVAR; export MYVAR; mycommand'

If even LC_* is not an option, you can pass information in the TERM environment variable, which is always copied (there may be a length limit however). You'll still have to make sure that the remote shell doesn't restrict the TERM variable to designate a known terminal type. Pass the -t option to ssh if you're not starting a remote interactive shell.

env TERM="extra information:$TERM" ssh -t server.example.com 'MYVAR=${TERM%:*}; TERM=${TERM##*:}; export MYVAR; mycommand'

Another possibility is to define the variable directly in the command:

ssh -t server.example.com 'export MYVAR="extra information"; mycommand'

Thus, if passing a local variable:

ssh -t server.example.com 'export MYVAR='"'$LOCALVAR'"'; mycommand'

However, beware of quoting issues: the value of the variable will be interpolated directly into the shell snippet executed on the remote side. The last example above assumes that $LOCALVAR does not contain any single quotes (').

13
  • 3
    Thanks much, I was in a rage that the stupid LC_* variables are exported on ssh and your answer directed me where to look. I just have to disable that in ~/.ssh/config Oct 9, 2012 at 16:37
  • 1
    I'm in the situation of the original poster, but the variable that I wanted forwarded is TERM, so I'm a bit puzzled by your answer. Has this automatic forwarding of TERM been disabled by recent OpenSSH versions?
    – Doub
    Nov 20, 2012 at 18:15
  • 1
    @Doub The default is to refuse all environment variables on the server side, with AcceptEnv directives in sshd_config as desired by the administrator. But TERM is treated specially, as far as I know there is no way to filter it on the server side (it is set in the environment of the shell regardless of any configuration setting). Are you sure there isn't a profile script overriding it (like /etc/profile or ~/.profile or ~/.bashrc)? Nov 20, 2012 at 18:24
  • 2
    @Gilles: I tested it again, and unless I explicitly add TERM to my AcceptEnv directive, the TERM is not passed on. I not opening a shell, but running a command directly, for example: "ssh -o SendEnv=TERM shell.example.com env". That prints all environment variable, and TERM only appears if it's in SendEnv on the client and AcceptEnv on the server. If I run "ssh -o SendEnv=TERM shell.example.com echo \${TERM}" withoug the AcceptEnv or SendEnv, it prints "dumb", which I'm not sure where it comes from (env doesn't even list TERM in that case).
    – Doub
    Nov 27, 2012 at 14:37
  • 7
    @Doub Oh, I see. TERM is only transmitted if the client requests the server to allocate a tty. If there's no terminal on the remote side, it would be useless to transmit TERM. When you specify a command, if you want to have a terminal on the remote side, you need the -t command line option (or RequestTTY in ~/.ssh/config). Nov 27, 2012 at 15:28
31

On your local client, in your ~/.ssh/config you can add SetEnv, e.g.

Host myhost
  SetEnv FOO=bar

Note: Check man ssh_config.

Then on the server, make sure to allow client to pass certain environment variables in your /etc/ssh/sshd_config config file:

AcceptEnv LANG LC_* FOO BAR*

Note: Check man sshd_config.

2
  • 2
    Worth noting, especially since OP asked about setting it to vary 'session to session', that, as with all ssh_config options, it can be set via flag too: ssh -o 'SetEnv FOO=bar BAR=foo' [...]. (If setting multiple vars, they must be supplied to a single -o 'SetEnv.)
    – OJFord
    Jan 18, 2021 at 13:06
  • I had a typical issue with allacritty so I used SetEnv term=xterm but also noticed on the Server that only AcceptEnv Lang LC_* was set. Why does it work?
    – MaKaNu
    Mar 11 at 12:52
21

If you can administrate the target host you can configure sshd to allow passing your local environment variables along to the target host.

From the sshd_config man page:

 PermitUserEnvironment
     Specifies whether ~/.ssh/environment and environment= options in
     ~/.ssh/authorized_keys are processed by sshd.  The default is
     "no".  Enabling environment processing may enable users to bypass
     access restrictions in some configurations using mechanisms such
     as LD_PRELOAD.

sshd configuration typically lives at /etc/ssh/sshd_config

1
  • 10
    It's very useful to know that this is set to "no" by default!
    – jathanism
    Oct 4, 2013 at 22:59
6

So, on your client, you have some environment variable, and you want that to be available to the remote command? I don't think there's a way to have ssh magically pass it along, but you can probably do something like this. Instead of using, say:

ssh remote.host my_command

You can do this:

ssh remote.host env ENV_VAR=$ENV_VAR my_command
4
  • 2
    "probably"? I wish I read that before actually trying this answer. Didn't work for me.
    – tishma
    Jan 4, 2017 at 23:54
  • 1
    Care to elaborate on any errors received, etc?
    – pioto
    Jan 30, 2017 at 2:30
  • 1
    @pioto Might be quoting, e.g. if ENV_VAR has spaces in it Apr 11, 2017 at 13:32
  • On Mac you have to have -t option for an interactive version, otherwise it looks stuck. So this might work: $ ssh -t remote.host env ENV_VAR=$ENV_VAR my_command
    – muenalan
    Jan 11, 2018 at 9:53
5

@emptyset's response (which didn't work for me) led me to this answer:

You can add this command to your ~/.ssh/authorized_keys file:

command="/usr/bin/env VARIABLE=<something> $SHELL" ssh-rsa <key>

export VARIABLE=<something> was immediately exiting, and the SSH connection was closed (locking me out of the server), whereas /usr/bin/env ... $SHELL will run your default shell with a modified environment.

4
  • This just hangs when logging in for me. When I remove it SSH returns to normal and I get my usual login shell. May 15, 2018 at 3:16
  • @NickSweeting have you tried maybe replacing $SHELL with an actual shell? Also check that /usr/bin/env exists on the server. However the solution is not perfect: I have noticed that it hang when I wanted to use scp or an inline command.
    – madprog
    May 15, 2018 at 15:50
  • Yes, that was the first thing I tried. Unfortunately never got it to work, ended up enabling PermitUserEnvironment yes and using environment="..." instead of command="...". May 16, 2018 at 17:59
  • This is working nicely for me.
    – Mr. Tao
    Apr 16, 2019 at 11:40
4

just one simple command :

ssh -t your_host_or_ip 'export some_var_name=whatever_you_want; bash'
2

I was making a custom build of OpenSSH for a device with a cramfs in the home directory and /etc (Cram FS is read-only) so ~/.ssh/environment would not work not without rebuilding the entire FS and these were field deployed devices(Embedded Systems Hence the use of CRAMFS). You can specify in the sshd_config the location of the authroized_keys file but for some reason the environment= only work for environment variables in the ~/.ssh/authroized_keys. Editing the /etc/profile wasn't an option and I had to load ssh in a non-standard directory. In session.c after the child_set_env(..."MAIL"...) just add the enviroment variables you need(This is a hack I know...) but just incase someone needs some hardcoded envs for a session if you are compiling from source you can do this. TGI-FLOSS

1
  • Don't forget you can bind-mount /tmp copies of files over their original source in a read-only file system. Sep 13, 2023 at 10:09
1

You could try invoking a custom command, assuming you have password-less ssh login setup. On the server, edit your ~/.ssh/authorized_keys entry that corresponds to the key from you client:

command="export VARIABLE=<something>" ssh-rsa <key>

Look at this link in the section Forced Command for a little more detail.

1
  • 1
    I tried this, but it doesn't work. It runs the command and exits, so there's no interactive session. Is that the normal behavior? If so, that could be useful if all you want to do is allow a specific key to trigger a specific command, but if you want to pass info that is used in a session (as the question states) then it is useless for that purpose. There IS no session.
    – iconoclast
    Nov 7, 2011 at 22:17
1

this worked for me

ssh gitvps "VAR=hello FOO=world ;" echo \$VAR \$FOO

you need to scape variables with spaces, semicolon and dollar signs ($) using symbol "\" and single cuotes

ssh gitvps "VAR='hello:sd%\ \;\ s%' FOO='34\$34=,rtrt' ;" echo \$VAR \$FOO
0

sometimes I go like this:

ssh [email protected] bash << EOF
export VAR1=$VAR1
export VAR2=$VAR2
./script-or-command-on-server.sh
EOF

you can put "sudo bash" as well if needed

it's nice because you can put each variable on its own line, and either hard code the value or get it from your local environment

1
  • if you have spaces or special characters or whatever, you might have to adjust the command or choose a different method
    – Alex028502
    Oct 21, 2022 at 12:21
0

If you wrap ssh with /bin/bash, you can pass things 1:1 similar to how you interact locally with a script:

# SSH destination 'script' args..
SSH()
{
  local ARGS;
  printf -v ARGS ' %q' "$2" -- "${@:3}";
  ssh "$1" -- "/bin/bash --noprofile --norc -c -- $ARGS";
}

This does not pass the local environment to the remote /bin/bash, but passes all arguments without changing them. (Passing the whole env usually is not what you want.)

Just use it as follows:

# To pass something as argument literally
SSH user@host 'cd "$1" && exec "${@:2}"' "$DIR" cmd args..

# to pass $envA and $envB to cmd with args..
SSH user@host 'envA="$1" envB="$2" exec "${@:3}"' "$envA" "$envB" cmd args..

# see what's passed and how:
SSH user@host 'printf "ARG: %q\\n" "$@"' '*' '`not dangerous`' $'\177' $'\n'

The latter prints

ARG: \*
ARG: \`not\ dangerous\`
ARG: $'\177'
ARG: $'\n'
  • Unlike ssh, arguments passed to SSH are not re-interpreted
    • No fear for unquoted shell characters to do harm
    • No fear for sudden globbing on the other side
    • Filenames containing strange things (like end in \n) are correctly passed
  • You must transfer environment variables yourself to the remote as args
    • generic environment passing is left as exercise for the reader
  • To use some ssh option, you need to alter SSH
    • generic ssh option passing is left as exercise for the reader

But:

  • This recipe needs /bin/bash on both sides.

You can adapt it to any other shell, of course.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .