n8n or a plain bash script — when is the workflow tool overkill
n8n is great for orchestration, but often bash + cron does the same faster and cheaper. Here's the rule I use and three examples of each from my setup.

I run n8n and ~30 bash scripts in ~/docker/scripts/. Some things I leave in n8n, others I deliberately keep in bash. Here's the rule I use and concrete examples of each.
Rule in one line
n8n when there are human-readable steps, integrations with 3+ external services, and someone else may edit. Bash when I have less than that, time matters, and I'm the only owner.
In practice: ~5 workflows live in n8n, ~30 scripts in bash. The ratio tells the story.
In n8n: 3 examples
1. Cold outreach pipeline
A workflow that for each new lead in the database:
- Fetches company data (Google Places API)
- Generates a brand brief (LLM call)
- Creates a demo page (HTTP to my deploy script)
- Sends an email with a link (Gmail API)
- Logs to Vikunja as a task
5 external systems, each with its own auth. n8n provides credential storage, retry logic, flow visualization. Bash would rot from if-else for fail-handling here.
2. Lead capture from a form
Webhook → validation → save to DB → welcome email → Telegram notify. Also 4 external integrations. n8n.
3. Prototyping new workflows
When I don't know if a flow makes sense, I prototype in n8n. Easy to add steps, easy to test. Once a workflow stabilizes and only has 1-2 integrations, sometimes I rewrite to bash.
In bash: 3 examples
1. Backup status check
#!/usr/bin/env bash
# /home/kkaletka/docker/scripts/backup-check.sh
LATEST=$(ls -t /backups/proxmox/ | head -1)
AGE_HOURS=$(( ($(date +%s) - $(stat -c %Y "/backups/proxmox/$LATEST")) / 3600 ))
if (( AGE_HOURS > 26 )); then
curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
-d "chat_id=${CHAT}" \
-d "text=⚠️ Last backup $AGE_HOURS h ago"
fi15 lines, one variable to change if something moves. n8n would build JSONs and webhooks here, overkill.
2. Trading dashboard alert
Cron every minute. SQL query, parse result, if PnL threshold crossed, Telegram. 30 lines of bash + sqlite3 CLI.
In n8n you'd need: trigger node (Cron), DB node (PostgreSQL), filter node, Telegram node. Each with configuration. Bash does it in one file.
3. Demo deploy
#!/usr/bin/env bash
# scripts/deploy-demo.sh
slug=$1
ssh forge "cd /home/kkaletka/demo-${slug} && docker compose up -d --build"That's the whole thing. n8n would need an SSH execute node, error handling. Bash does it in 4 lines.
What n8n costs
Concrete downsides:
1. Memory. My n8n container runs at ~512MB RAM. Bash uses 0 (exits immediately).
2. Versioning. Workflows are stored as JSON in n8n's DB. Export-import works, but code review is weak. Bash in git = full diff.
3. New-workflow startup time. A simple bash takes 5 minutes. An n8n workflow, 15-20 (clicking, wiring nodes, testing).
What bash costs
Not free either:
1. No retry/error handling out of the box. You write set -e, trap, retry-with-backoff by hand.
2. No GUI for non-technical folks. If someone else edits, n8n wins.
3. Weaker OAuth service integration. n8n has ready nodes for Gmail/Slack/etc. Bash needs full token-refresh handling on its own.
Decision rule
Questions I ask myself:
- How many external integrations? ≤ 2 → bash. ≥ 3 → consider n8n.
- Will the workflow change? Often → n8n. Stable → bash.
- Who will maintain it? Just me → bash. Someone else too → n8n.
- Do I need a webhook listener? Bash can (with
ncor a dedicated server), but n8n does it natively.
Most of my automations are "1 integration, I-maintain, stable" → bash. The minority is "many systems, evolution, others sometimes look" → n8n.
n8n isn't "better" or "worse" than bash. They're two different tools for two different task classes. Choose deliberately, not from a fashion for visual workflows.