Postgres node over SSH tunnel hangs indefinitely after the shared SSH client is closed
Bug Description
Postgres nodes using an SSH-tunnelled credential can hang forever. They never error and never retry, and retryOnFail / onError: continueErrorOutput don't fire. Only the workflow timeout or a manual cancel stops them. While it's hanging, nothing reaches the SSH server or the database.
It happens when two Postgres nodes with different typeVersions (e.g. 2.5 and 2.6) use the same SSH credential:
ConnectionPoolManagerkeys pools by node version, so they get two pools.SSHClientsManagerkeys SSH clients by host/user/key only, so both pools share one SSH client.
Only the pool that created the SSH client is subscribed to its lifecycle (the existing branch in SSHClientsManager.getClient ignores the caller's abortController).
When that pool is evicted after its 5-min TTL, its abort closes the shared SSH client while the other pool is still using it. The surviving pool keeps the closed client. The next query connects to the local TCP proxy, and sshClient.forwardOut() throws Error: Not connected synchronously (ssh2 1.15.0, lib/client.js:1401) inside the proxy's connection listener. That throw ends up in the global uncaughtException handler. The accepted socket is never piped or destroyed, so pg waits on it forever.
Two related details:
- The node's
connectionTimeoutoption doesn't help, because it isn't part of the pool cache key. A pool keeps the timeout of whichever node created it. - The
forwardOuterror-callback path has the same socket leak: it closes the proxy server but not the socket it already accepted.
To Reproduce
- Create a Postgres credential with SSH Tunnel enabled.
- Workflow A: Postgres node
typeVersion2.5, Execute QuerySELECT 1. Workflow B: Postgres nodetypeVersion2.6,SELECT 1. - Run A once, then B within a few seconds. B reuses A's SSH client, and the SSH server shows only one session.
- Keep running B every ~30s. Don't run A again.
- About 5 min after A's last run, A's pool is evicted and the SSH session closes. Every following run of B hangs, and
Error: Not connectedis reported as an uncaught exception.
We reproduced this on n8nio/n8n:2.41.4 against a local postgres:16 and an OpenSSH server, using n8n's compiled SSHClientsManager and configurePostgres. Excerpt:
[t+272.5s] B (v2.6): ok (174ms)
[t+300.3s] ConnectionPoolManager: Found stale pool. Cleaning it up.
[t+300.3s] Got abort signal, cleaning up ssh client.
[t+302.5s] uncaughtException: Not connected
[t+322.6s] B (v2.6, connectionTimeout 10): HUNG (no result after 20s)
... every later query: "Not connected" + hang
### Expected behavior
If the tunnel is gone, the query should fail promptly with an error, so retries and error outputs work. The next execution should open a fresh SSH connection.
### Debug Info
- n8n version: 2.41.4 (n8n Cloud)
- ssh2: 1.15.0
- Database: PostgreSQL 16 (AWS RDS) behind an OpenSSH bastion
### Operating System
n8n cloud
### n8n Version
2.41.4
### Node.js Version
26.7.0
### Database
PostgreSQL
### Execution mode
main (default)
### Hosting
n8n cloudSource: n8n-io/n8n