Description
n8n-nodes-custom-exec
Run a command on a remote execution service from n8n, over HTTP.
Some jobs need a real shell and real tooling — ffmpeg, imagemagick, pandoc,
yt-dlp, a font stack — that the n8n image deliberately does not ship. The usual
answer is to stand up a small sidecar: an HTTP service that accepts a command,
runs it, and returns the result. This node is the n8n end of that arrangement. You
point it at your service with a credential, give it a command (n8n expressions and
all), and it hands back the exit code, output and duration.
The connection lives entirely on a Remote Exec API credential — base URL plus
an optional shared secret — so the node reads nothing from the environment. That is
both what n8n’s node verification requires and what lets one n8n instance talk to
several exec services.
> Built and maintained by Shadow Software — we run
> n8n in production across a family of products and open-source the nodes we rely on.
> See our other node, n8n-nodes-huggingface-space,
> for running AI models from any Hugging Face Space.
Installation · Credentials · The exec service · Usage · Try it · Response · Security · Compatibility
Installation
Follow the community nodes installation guide,
then search for n8n-nodes-custom-exec.
Self-hosted, from the CLI:
npm install n8n-nodes-custom-exec
Credentials
The node requires a Remote Exec API credential with two fields:
- Base URL — where your exec service lives, e.g.
http://exec-sidecar:8080. - Token (optional) — a shared secret. When set, it is sent as the
Commands are POSTed to {baseUrl}/exec.
X-EXEC-TOKEN header on every request; leave it empty if the service is
unauthenticated.
The credential’s Test button issues a GET {baseUrl}/health, so your service
should answer that path with a 200 for the test to pass. Testing never runs a
command.
The exec service
The exec service is yours to run — the node is only the n8n client. Any HTTP server
works as long as it honours this tiny contract:
| Route | Purpose |
| — | — |
| POST /exec | body { "command": string, "timeout": number } → { "exitCode": number, "stdout": string, "stderr": string, "durationMs": number } |
| GET /health | 200 when healthy (used by the credential’s Test button) |
A minimal reference implementation is a few dozen lines. For example, in Node:
import express from 'express';
import { exec } from 'node:child_process';const app = express();
app.use(express.json());
const TOKEN = process.env.EXEC_TOKEN; // set the same value on the credential
app.get('/health', (_req, res) => res.sendStatus(200));
app.post('/exec', (req, res) => {
if (TOKEN && req.get('X-EXEC-TOKEN') !== TOKEN) return res.sendStatus(401);
const { command, timeout = 300 } = req.body ?? {};
const started = Date.now();
exec(command, { timeout: Math.min(timeout, 1800) * 1000, maxBuffer: 64 << 20 },
(err, stdout, stderr) => res.json({
exitCode: err?.code ?? 0,
stdout, stderr,
durationMs: Date.now() - started,
}));
});
app.listen(8080);
Run it in a container that has the tooling you need (ffmpeg, fonts, imagemagick,
…) alongside n8n, mount a shared volume into both, and point the credential at it.
Read the Security section before you expose it anywhere.
Usage
Set Command to whatever you want to run. n8n expressions are interpolated, so
you can build the command from earlier items:
ffmpeg -y -i /shared/{{ $json.inputFile }}
-vf "scale=1280:-1" /shared/{{ $json.outputFile }}
If both the exec service and n8n mount the same /shared volume at the same path,
files written by one are visible to the other, so a workflow can drop a file, run a
command against it, and pick the result back up.
extra 30 seconds on the HTTP call itself so a job that runs right up to its
deadline still returns its result rather than being cut off in transit.
failing the node.
stdout and stderr as separate fields rather than a single output field.
Try it — a one-click demo workflow
Every field below is set through the node’s real UI. Wire a Manual Trigger into
Remote Exec, pick or create a Remote Exec API credential (Base URL + optional
Token), write a Command, click Execute workflow.
(Watch it run)
▶ Full video (MP4)
·
same file under /assets/images
Open the node → bind the credential → write the command → execute:



Response
Each item gains an exec object:
{
"exec": {
"command": "ffmpeg -i /shared/in.png … /shared/out.webp",
"exitCode": 0,
"durationMs": 1834,
"output": "…stdout…", // single-field mode (default)
"stderr": "…" // present only when the command wrote to stderr
}
}
With Return Full Output on, output is replaced by separate stdout and
stderr fields. The item’s existing JSON is preserved alongside exec.
Security
**This node runs arbitrary shell commands on whatever service the credential points
at.** Anyone who can edit the workflow can run any command that service allows,
with that service’s privileges and filesystem access.
trust — never a shared or public endpoint.
X-EXEC-TOKEN. Do not expose the service unauthenticated.
volume, no host networking), and enforce your own timeout ceiling on its side —
the client-supplied timeout is a request, not a guarantee.
Treat the exec service as a remote shell, because that is exactly what it is.
Compatibility
Tested against n8n 1.x.
Dependencies
The node has zero runtime dependencies — nothing is shipped but the compiled
node itself, so a plain npm audit --omit=dev reports no vulnerabilities.
A plain npm audit does report advisories. Every one of them comes from
n8n-workflow, which is a peer dependency: n8n supplies it at runtime from its
own tree, so those advisories are resolved by upgrading n8n, not this package.
Links
We also build and open-source a family of WordPress plugins and themes for
WooCommerce stores: Broadside
(theme) and Broadside Blocks,
Crypto for WooCommerce, and
AGT for WooCommerce.
About
Made by Shadow Software — we build and run
automation-heavy SaaS products and open-source the n8n nodes we depend on. If you
need custom n8n nodes, workflow automation, or a platform built around it, get in
touch at shadowsoftware.com.
