Run Multiple Commands Over SSH on Linux

Deepak Prasad
Tested on RHEL 10.2 (Coughlan) — vm2.lab.example (192.168.56.220) as SSH client; vm1.lab.example (192.168.56.116) as remote host
Package openssh-clients 9.9p1-25.el10_2 and openssh-server 9.9p1-25.el10_2
Applies to RHEL, Rocky Linux, AlmaLinux, Fedora, Ubuntu, Debian, and other Linux systems with OpenSSH
Privilege SSH login to the remote host with permission to run the commands you chain together
Scope One-shot remote command chains with ;, &&, ||, subshell grouping, bash -s, here documents, and brief background & notes. Does not cover long-running jobs that must survive disconnect or SSH multiplexing.
Related guides ssh command
Long-running commands over SSH
SSH multiplexing
Copy folder over SSH
Test SSH connection

A single ssh invocation can run several remote steps in one session when you chain them with shell operators inside one quoted command string.

The examples below were run from vm2.lab.example using the lab-server host alias (pointing at vm1.lab.example). Replace that name with your own user@host or SSH config alias.


Quick answer

Goal Pattern
Always run the next command ssh lab-server 'cmd1; cmd2'
Run next only on success ssh lab-server 'cmd1 && cmd2'
Run fallback on failure ssh lab-server 'cmd1 || cmd2'
Run an existing local script remotely ssh lab-server 'bash -s' < script.sh
Run several inline lines ssh lab-server <<'EOF'EOF

Wrap the entire remote command in single quotes on the client so your local shell does not interpret ;, &&, $variables, or redirects before SSH runs.


How SSH executes multiple commands

When you supply a command after the hostname:

bash
ssh lab-server 'echo hello; hostname'

SSH sends that command to the remote host, where the remote user's shell interprets operators such as ;, &&, ||, pipes, and redirects. OpenSSH documents this as executing the specified command on the remote host instead of starting an interactive login session.

All chaining in this article happens on the remote side inside that one command string. Two details matter for later sections:

  • cmd1 & cmd2 & backgrounds jobs in the same remote shell. It does not launch separate SSH connections, and the client may still wait until background file descriptors on the SSH channel close. For work that must continue after you disconnect, use the patterns in long-running commands over SSH.
  • Exit status: with semicolon-separated commands, the SSH client exit code reflects the last command in the chain, not an earlier failure in the middle.

Use single quotes around the remote command when you need remote shell variables expanded on the server:

bash
ssh lab-server 'echo "$HOME"; hostname'

Sample output:

output
/root
vm1.lab.example

Single quotes stop your local shell from substituting $HOME; the remote shell expands it.


Run every command with semicolon (;)

Use semicolon when each step should run even if a previous step failed:

bash
ssh lab-server 'echo first; echo second; false; echo third'

Sample output:

output
first
second
third

The failed false in the middle did not stop echo third. Check whether the chain failed overall:

bash
ssh lab-server 'echo first; false; echo third'; echo "client exit: $?"

Sample output:

output
first
third
client exit: 0

The client reports exit 0 because the last command (echo third) succeeded, even though false ran in between.


Stop on failure with &&

Use && when later steps depend on earlier ones succeeding:

bash
ssh lab-server 'mkdir -p /tmp/ssh-multi-lab && cd /tmp/ssh-multi-lab && printf "ok\n" > demo.txt && cat demo.txt'

Sample output:

output
ok

If any step returns non-zero, the rest of the chain is skipped:

bash
ssh lab-server 'false && echo should-not-run'

Sample output:

No output appears because false failed first. Clean up the lab directory when you are done:

bash
ssh lab-server 'rm -rf /tmp/ssh-multi-lab'

Run a fallback with ||

Use || when the second command should run only if the first fails:

bash
ssh lab-server 'test -f /etc/shadow || echo missing-file'

Sample output:

On a normal system /etc/shadow exists, so the echo does not run. A failing test triggers the fallback:

bash
ssh lab-server 'test -f /no/such/file || echo fallback-ran'

Sample output:

output
fallback-ran

Group commands and control cd

cd in a remote chain affects every later command in the same shell unless you isolate it.

Without grouping, both pwd lines run under /tmp/ssh-multi-lab after the first cd:

bash
ssh lab-server 'mkdir -p /tmp/ssh-multi-lab && cd /tmp/ssh-multi-lab && pwd; cd /var && pwd'

Sample output:

output
/tmp/ssh-multi-lab
/var

Parentheses run the grouped commands in a subshell, so cd does not leak out:

bash
ssh lab-server '( cd /tmp/ssh-multi-lab; pwd ); pwd'

Sample output:

output
/tmp/ssh-multi-lab
/root

Braces { cd …; cmd; } group commands in the current shell, so a cd inside the group persists afterward — the opposite of parentheses. Prefer ( … ) when you only want a temporary working directory for one step.

Remove the lab directory:

bash
ssh lab-server 'rm -rf /tmp/ssh-multi-lab'

Run a local script with bash -s

When the commands already live in a file on your client, redirect that file into bash -s on the remote host:

bash
ssh lab-server 'bash -s' < local.sh

bash -s tells Bash on the remote host to read the script from standard input. The file does not have to exist on the server disk.

Create local.sh on your workstation with echo from-script and uname -r, then run the redirect above. Sample output from the lab:

output
from-script
6.12.0-211.47.1.el10_2.x86_64

Use this pattern when you maintain reusable automation locally and want one SSH invocation to execute it remotely.


Run multiple lines with a here document

When the commands are inline in your shell script or terminal — not saved in a separate file — attach a here document to ssh:

bash
ssh lab-server <<'REMOTE'
echo from-heredoc
pwd
REMOTE

Sample output (login banner lines omitted):

output
from-heredoc
/root

Quote the delimiter (<<'REMOTE') when you do not want the client shell to expand $variables before the text reaches SSH. Add -T when you want to disable pseudo-tty allocation for cleaner non-interactive output.

Summary of the two multi-line patterns:

You have Use
An existing script file on the client ssh host 'bash -s' < script.sh
Several inline lines in the current shell ssh host <<'EOF'EOF

Background commands with &

Single & sends each command to the background inside the same remote shell:

bash
time ssh lab-server 'echo a & sleep 2 & wait'

Sample output:

output
a

real    0m2.651s
user    0m0.011s
sys     0m0.020s

The client waited until wait collected the background jobs. Without wait, behavior depends on how the jobs connect to the SSH channel — the client may still block. Treat & as a shell background operator, not as a way to fire off independent remote sessions.

For jobs that must keep running after SSH exits, use nohup, setsid, or tmux as described in long-running commands over SSH.


Troubleshooting

Symptom Likely cause Fix
Only the first command runs Local shell parsed ; or && before SSH Wrap the full remote command in single quotes: ssh host 'cmd1; cmd2'
Later commands never run Earlier step failed in a && chain Use ; if every step must run, or fix the failing command
cd affects unexpected later commands No subshell around the directory change Wrap temporary cd in ( … )
Variables expand on the wrong host Double quotes on the client let the local shell expand $var Use single quotes around the remote command: ssh host 'echo "$HOME"'
SSH hangs after cmd & Background job still tied to SSH stdio Add wait, redirect I/O, or use detached execution
Here document runs locally Missing ssh host before << The heredoc must be attached to the ssh command, not a bare shell block

References


Summary

Multiple commands over SSH are shell chaining inside one remote command argument. Use semicolon when every step must run, && when later steps depend on success, and || for fallbacks. Wrap temporary cd commands in ( … ) so the working directory does not leak into the rest of the chain.

Use single quotes on the client when remote variables such as $HOME should expand on the server. When the commands live in a local file, run ssh host 'bash -s' < script.sh. When you paste several inline lines in the current shell, attach a here document to ssh. Background & stays inside one remote shell — it is not a substitute for detached long-running jobs.

Pick the operator that matches how failures should behave, keep the remote string in single quotes unless you deliberately want local expansion, and verify once with a harmless chain before you point the pattern at production paths.


Frequently Asked Questions

1. How do I run multiple commands in one ssh line?

Pass a single quoted remote command string: ssh host "cmd1; cmd2" runs both on the remote host. Use && when a later step should run only if the previous command succeeded.

2. What is the difference between semicolon and && over SSH?

Semicolon runs every command regardless of exit status. Double ampersand runs the next command only when the previous one returned exit code zero.

3. Should I use single ampersand to run SSH commands in parallel?

A single & backgrounds jobs inside one remote shell session. It does not open separate SSH connections, and the client may still wait for background jobs tied to the SSH channel. For long detached work, see nohup, setsid, or tmux instead.

4. When should I use bash -s with SSH?

Run ssh host bash -s < local.sh when the commands already live in a file on your workstation. Bash on the remote host reads the script from standard input and executes it without copying the file to the server first.

5. Do parentheses or braces change the working directory on the remote host?

Parentheses start a subshell, so cd inside ( ) does not affect later commands. Braces group commands in the current shell, so cd inside { } persists for commands after the group.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration