Four failures that all pointed away from each other
The first failure of the evening looked like a video pipeline having a bad day: the SSH node "Copy Video to Public Host" died with ECONNRESET, and a manual retry two minutes later failed again, this time at an earlier step, "Fetch Watermarked Video," with "Connection lost before handshake." Different node, different error text, same SSH-layer failure — that pattern alone would normally point toward network flakiness specific to that one workflow's file-transfer steps.
Two hours later, a completely different picture: a football-content publisher's "SSH - Read Segment Manifest" node and a crypto-options trading bot's SSH-based iteration step both failed within 30 seconds of each other, both throwing the exact same error text — Timed out while waiting for handshake — straight from the underlying ssh2 client library. A finance-content publisher failed at the same mark too, both on its own schedule and on an immediate manual retry. None of these four workflows share a credential's usage pattern, a Sheet, a video pipeline, or even a product. The football publisher was trying to read a manifest file. The trading bot was trying to run a paper-trading iteration script. Neither has anything in common with the other except that both dial out over SSH to the same server.
The tell: every failure died before doing any work
Looking at each execution's path individually, the pattern is consistent: every single one failed at the SSH connection step itself, before the remote command it was trying to run had a chance to execute at all. Not a bad file path, not a permissions error, not a command that ran and returned a bad exit code — the TCP/SSH handshake itself never completed, or completed and then got reset mid-session. That's a strong signal to stop looking at any individual workflow's own logic and start looking at the shared thing underneath all of them: the SSH server itself, and the network path to it.
What was actually happening on the server
sshd's own status at the time showed 11 of 10-100 startups — a count of concurrent, not-yet-authenticated connections that had crossed the server's default MaxStartups threshold of 10. Alongside it, a burst of anonymous connection attempts was landing every few seconds, the unmistakable shape of an automated brute-force scan hitting the SSH port from across the internet, not a targeted attack on any credential used by these workflows specifically. fail2ban was installed and actively banning offending IPs, but not fast enough to keep the concurrent-connection count under the threshold during the burst — once MaxStartups is hit, the SSH daemon starts refusing or delaying every new connection attempt, including completely legitimate ones from n8n's own automation, indiscriminately.
That's the mechanism behind both failure shapes seen that evening: a connection that gets a slot but then has it yanked mid-negotiation looks like ECONNRESET or "Connection lost before handshake"; a connection that can't get a slot at all just sits and eventually times out client-side as "Timed out while waiting for handshake." Same root cause, two different symptoms depending on exactly when in the flood each workflow's SSH node happened to try connecting.
Why this is genuinely hard to diagnose from inside any one workflow
Debugging any single one of these executions in isolation, with no visibility into what else was happening on the server at that moment, offers no way to reach the real cause. Every clue available inside the n8n execution log — the node name, the error text, the workflow's own recent history — points toward that workflow's own SSH step being unreliable. The only thing that actually reveals the shared cause is noticing that multiple, otherwise-unrelated workflows failed at the SSH layer within the same short window, which requires looking at execution history across the whole instance rather than debugging one workflow at a time.
What actually helps
Two independent layers, not one fix: retryOnFail with a short delay on every SSH node across every workflow that depends on this server, so a transient connection-slot refusal during a flood gets absorbed instead of failing the whole run outright — this alone recovers most of these incidents without any human involved. Separately, and not something a workflow-level fix can touch: hardening the SSH server itself against exactly this class of flood, since fail2ban alone wasn't reacting fast enough to prevent MaxStartups saturation during a genuine burst. The generalizable lesson: when two or more workflows that share nothing except a common piece of infrastructure fail around the same time, check that shared infrastructure's own health before touching any workflow's logic — the failure signature will look local to each one, but the fix never is.
n8n Automation Hub