DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n SSH Node: Run Remote Commands and Transfer Files in Your Workflows [Free Workflow JSON]

If you run n8n self-hosted and need to deploy code, restart services, pull logs, or sync files on a remote Linux server, the SSH node lets you do all of it directly inside your workflow — no external CI pipeline required.

This guide covers every operation, credential setup, common patterns, gotchas, and a free workflow JSON you can import today.


What the SSH Node Does

The n8n SSH node connects to a remote server over SSH and lets you:

  • Execute Command — run any shell command or script and capture stdout/stderr
  • Download File — pull a file from the remote server into the workflow as binary data
  • Upload File — push binary data from the workflow to a path on the remote server

All three operations use the same SSH credential (username + password or private key).


Credential Setup

Option A — Password Authentication

  1. Go to Credentials → New → SSH.
  2. Fill in:
    • Host: your server IP or hostname
    • Port: 22 (default)
    • Username: your SSH user (e.g., ubuntu, ec2-user)
    • Authentication Type: Password
    • Password: your SSH password

Password auth works but is less secure. Use key-based auth for production.

Option B — Private Key Authentication (Recommended)

  1. Generate a key pair if you don't have one:
   ssh-keygen -t ed25519 -C "n8n-automation"
Enter fullscreen mode Exit fullscreen mode
  1. Append ~/.ssh/id_ed25519.pub to ~/.ssh/authorized_keys on the remote server.
  2. In n8n credentials:
    • Authentication Type: Private Key
    • Private Key: paste the full contents of ~/.ssh/id_ed25519 (including -----BEGIN...-----END lines)
    • Passphrase: if your key has one

Gotcha: n8n expects the private key in OpenSSH format. If you generated an older RSA key with PuTTYgen (.ppk format), convert it first:

puttygen key.ppk -O private-openssh -o id_rsa
Enter fullscreen mode Exit fullscreen mode

Operations

Execute Command

Runs a shell command on the remote server and returns stdout, stderr, and exit code.

Node settings:

  • Operation: Execute Command
  • Command: any shell command string (e.g., systemctl restart nginx)

Output fields:
| Field | Type | Description |
|-------|------|-------------|
| stdout | string | Command standard output |
| stderr | string | Command standard error |
| code | number | Exit code (0 = success) |

Check for errors with an IF node:

{{ $json.code !== 0 }}
Enter fullscreen mode Exit fullscreen mode

Use Continue On Fail on the SSH node so the workflow doesn't halt on non-zero exits — then gate on code manually.

Multi-command tip: Chain commands with && for stop-on-failure or ; to run all regardless:

cd /var/app && git pull origin main && npm install && pm2 restart app
Enter fullscreen mode Exit fullscreen mode

Download File

Pulls a file from the remote server into the workflow as a binary item. Useful for ingesting log files, database dumps, or report exports.

Node settings:

  • Operation: Download File
  • Path: absolute path on the remote server (e.g., /var/log/app/error.log)
  • Binary Property: name to store the file under in $binary (default: data)

The file lands in the workflow as binary data. Chain it to:

  • Spreadsheet File node to parse a CSV
  • Move Binary Data to rename the property
  • Send Email / Slack to attach and send
  • Google Drive / S3 to archive

Upload File

Pushes a binary file from the workflow to a path on the remote server.

Node settings:

  • Operation: Upload File
  • Binary Property: the $binary key holding the file (default: data)
  • Path: destination absolute path on the remote server (e.g., /var/app/data/import.csv)

The remote directory must exist. Create it in a preceding Execute Command step:

mkdir -p /var/app/data
Enter fullscreen mode Exit fullscreen mode

Common Patterns

1. Automated Deploy on Schedule

Trigger a deploy every weekday at 3 AM without touching a CI/CD pipeline.

Nodes: Schedule Trigger → SSH (git pull + restart) → IF (exit code check) → Slack (success/failure alert)

# Command
cd /var/www/myapp && git pull origin main && npm ci && pm2 restart myapp
Enter fullscreen mode Exit fullscreen mode

If code !== 0, the IF node routes to a Slack error message with $json.stderr.


2. Log Harvesting and Alerting

Pull the last 100 lines of an application log and scan for ERROR keywords.

Nodes: Schedule Trigger → SSH (Execute Command: tail -100 /var/log/app/app.log) → Code node (filter lines containing "ERROR") → IF (errors found?) → Slack alert

// Code node: extract error lines
const lines = $json.stdout.split('\n');
const errors = lines.filter(l => l.includes('ERROR'));
return [{ json: { errors, count: errors.length } }];
Enter fullscreen mode Exit fullscreen mode

3. Remote File Export and Archive

Run a database dump on the remote server, download the file, and archive it to S3.

Nodes:

  1. SSH (Execute Command): mysqldump -u root -p"$DB_PASS" mydb > /tmp/backup_$(date +%Y%m%d).sql
  2. SSH (Execute Command): ls /tmp/backup_*.sql | tail -1 → capture filename in stdout
  3. SSH (Download File): path from step 2's stdout
  4. S3 (Upload): push binary to backups/ prefix
  5. SSH (Execute Command): rm /tmp/backup_*.sql → cleanup

Gotchas

Issue Root Cause Fix
All configured authentication methods failed Wrong key type or passphrase Check OpenSSH format; verify passphrase; test with ssh -i key user@host manually
ECONNREFUSED Port 22 blocked or sshd not running Check firewall rules and systemctl status sshd
Commands run but environment variables missing SSH non-interactive shell doesn't load .bashrc Source explicitly: bash -lc 'your command' or use full paths (e.g., /usr/local/bin/node)
sudo commands hang sudo needs a TTY Either add NOPASSWD to sudoers or use `echo 'pass'
File download truncated Large file, timeout Compress before downloading: {% raw %}gzip /tmp/backup.sql then download .gz; decompress with n8n Compression node
Path does not exist on Upload Remote directory missing Add an Execute Command step to mkdir -p /your/path before uploading
Binary property name mismatch Upload looking for wrong key Confirm Binary Property matches what the previous node outputs (inspect with Move Binary Data)

Security Notes

  • Never hardcode passwords or passphrases in the Command field — they appear in execution logs. Store secrets in n8n Variables ($vars.DB_PASS) or environment variables and reference them.
  • Least privilege: Create a dedicated n8n-bot user on the remote server with only the permissions it needs. Avoid running workflows as root.
  • Audit trail: Consider logging each SSH command execution to a Google Sheet or database for compliance.
  • Key rotation: Rotate the n8n SSH key on the same schedule as your other service keys.

Free Workflow JSON

Import this starter workflow: Schedule → SSH deploy → exit code check → Slack alert.

{
  "name": "SSH Deploy + Slack Alert",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 3 * * 1-5" }] } },
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "executeCommand",
        "command": "cd /var/www/myapp && git pull origin main && npm ci --production && pm2 restart myapp"
      },
      "name": "SSH Deploy",
      "type": "n8n-nodes-base.ssh",
      "credentials": { "sshPassword": { "id": "1", "name": "My SSH Credential" } },
      "position": [460, 300]
    },
    {
      "parameters": { "conditions": { "number": [{ "value1": "={{ $json.code }}", "operation": "equal", "value2": 0 }] } },
      "name": "IF Success",
      "type": "n8n-nodes-base.if",
      "position": [680, 300]
    },
    {
      "parameters": { "text": "✅ Deploy succeeded at {{ $now.toISO() }}" },
      "name": "Slack OK",
      "type": "n8n-nodes-base.slack",
      "position": [900, 200]
    },
    {
      "parameters": { "text": "❌ Deploy FAILED (exit {{ $json.code }}):\n{{ $json.stderr }}" },
      "name": "Slack Error",
      "type": "n8n-nodes-base.slack",
      "position": [900, 400]
    }
  ],
  "connections": {
    "Schedule Trigger": { "main": [[{ "node": "SSH Deploy", "type": "main", "index": 0 }]] },
    "SSH Deploy": { "main": [[{ "node": "IF Success", "type": "main", "index": 0 }]] },
    "IF Success": {
      "main": [
        [{ "node": "Slack OK", "type": "main", "index": 0 }],
        [{ "node": "Slack Error", "type": "main", "index": 0 }]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Want more? The n8n Workflow Starter Pack includes 10 production-ready workflows covering deploy automation, log monitoring, database backups, and more — all with credentials wired up and error handling included. $29, one-time.


Summary

Operation Use case
Execute Command Deploy code, restart services, run scripts, harvest logs
Download File Pull logs, exports, database dumps into the workflow
Upload File Push CSVs, configs, or generated reports to a remote server

The SSH node is one of the most powerful nodes in n8n for self-hosted infrastructure automation. Combine it with Schedule Trigger for unattended deploys, with Slack/Email for alerts, and with S3/Drive for off-server archiving.

What are you running via SSH in your workflows? Drop a comment below — I read every one.

Top comments (1)

Collapse
 
pirateprentice profile image
Pirate Prentice

Are you using the SSH node for deploys, log harvesting, or DB dumps? Would love to hear what remote automation you've wired up — drop your use case below.