How to Run n8n in a Cloud Sandbox With a Working Webhook URL
Updated October 27, 2026 .md
Short answer: Run n8n's Docker image in a Sandboxer sandbox with a volume and --restart unless-stopped, set N8N_HOST, N8N_PROTOCOL and N8N_WEBHOOK_URL to the hosted address, then run sbx host 5678. The editor and webhooks answer on https://
At a glance#
Everything here ran on one small sandbox (2 vCPU, 4 GB) on October 27, 2026, with n8n 2.45.4. Single runs, not benchmarks.
| Step | What we saw |
|---|---|
sbx new --type small |
Ready in 3.2 seconds |
| n8n start with an existing volume | "Editor is now accessible" after 17.4 seconds |
| Memory at idle | 731 MiB |
| Hosted URL, no token | 401 from the Sandboxer proxy |
| Hosted URL with the token | 302, then the n8n editor (<title>n8n.io - Workflow Automation</title>) |
| Webhook call through the hosted URL | Median 225 ms over 20 calls from outside (each a new TLS connection), 64 ms from inside the sandbox |
sbx stop --wait, then sbx resume |
memoryRestored: true, same n8n PID, webhook answered 223 ms after resume returned |
Cold boot (resume as default) |
memoryRestored: false, Docker restarted n8n, webhook answered after 16.9 seconds, workflow and owner account kept |
Start n8n with the hosted address#
n8n's Docker install docs start the container with a n8n_data volume mounted at /home/node/.n8n. We used the same image and variables, swapped -it --rm for -d --restart unless-stopped so it survives reboots, and added the URL settings.
A sandbox's hosted URL is https://<subdomain>-<port>.box.sandboxer.com, and sbx new prints the subdomain. Ours was silent-stormy-mint, so n8n's address is https://silent-stormy-mint-5678.box.sandboxer.com. Set it before you start n8n:
sbx new --name n8n-demo --type small
id=sb_68ufa2y5te # from sbx new
N8N_URL=silent-stormy-mint-5678.box.sandboxer.com # <subdomain>-5678.box.sandboxer.com
sbx exec $id "docker volume create n8n_data"
sbx exec $id --timeout 590 "docker pull docker.n8n.io/n8nio/n8n"
sbx exec $id "docker run -d --name n8n --restart unless-stopped \
-p 5678:5678 \
-e GENERIC_TIMEZONE=UTC -e TZ=UTC \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_HOST=$N8N_URL -e N8N_PROTOCOL=https \
-e N8N_WEBHOOK_URL=https://$N8N_URL/ \
-e N8N_PROXY_HOPS=1 \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n"
sbx exec $id "docker logs n8n 2>&1 | grep -A1 'Editor is now'"Editor is now accessible via:
https://silent-stormy-mint-5678.box.sandboxer.comWhy each URL setting matters, per n8n's webhook URL docs: n8n builds webhook URLs from N8N_PROTOCOL, N8N_HOST and N8N_PORT. Behind a proxy that serves port 443, that gives the wrong address, so set the webhook URL yourself and N8N_PROXY_HOPS=1. Sandboxer's hosting proxy is that proxy.
Use N8N_WEBHOOK_URL, not WEBHOOK_URL. We first started with WEBHOOK_URL, and n8n 2.45.4 logged:
There are deprecations related to your n8n setup. Please take the recommended actions to update your configuration:
- WEBHOOK_URL -> Use N8N_WEBHOOK_URL instead, which sets the base URL for both test and production webhooks.The log also said the Python task runner couldn't start because the image has no Python 3. That comes from n8n's image, not the sandbox; our test workflow has no Code node.
The pull can exceed sbx exec's default wait, so give it --timeout (up to 600 seconds) or --detach it.
Host port 5678 and open the editor#
sbx host $id 5678 --title n8nThe URL it prints ends in ?_token=<token>. That token gates the whole port at the Sandboxer proxy:
B=https://silent-stormy-mint-5678.box.sandboxer.com
curl -s -o /dev/null -w "%{http_code}\n" $B/
# 401
curl -s -D - -o /dev/null -c jar.txt "$B/?_token=<token>" | grep -iE "^HTTP|^location"
# HTTP/2 302
# location: /
curl -s -b jar.txt $B/ | grep -o "<title>[^<]*</title>"
# <title>n8n.io - Workflow Automation</title>
curl -s -b jar.txt $B/healthz
# {"status":"ok"}In a browser, open the full token link once. It sets a 30-day cookie and the editor loads.
n8n has its own login on top. On a fresh volume, /rest/settings reported "showSetupOnFirstLoad":true: the first person to open the editor creates the owner account. Create it while the port is still private. Behind the token, n8n's own API answered {"status":"error","message":"Unauthorized"} until we signed in.
Import a webhook workflow with the n8n CLI#
The workflow has two nodes: a Webhook trigger on POST /webhook/hello, and a Set node whose output becomes the response.
[
{
"id": "helloWebhook01",
"name": "Hello webhook",
"active": false,
"nodes": [
{
"id": "9f1c2a3b-0000-4000-8000-000000000001",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "9f1c2a3b-0000-4000-8000-0000000000aa",
"parameters": { "httpMethod": "POST", "path": "hello", "responseMode": "lastNode", "options": {} }
},
{
"id": "9f1c2a3b-0000-4000-8000-000000000002",
"name": "Reply",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [220, 0],
"parameters": {
"assignments": { "assignments": [
{ "id": "a1", "name": "greeting", "type": "string", "value": "={{ 'hello ' + $json.body.name }}" },
{ "id": "a2", "name": "receivedAt", "type": "string", "value": "={{ $now.toISO() }}" }
] },
"options": {}
}
}
],
"connections": { "Webhook": { "main": [[{ "node": "Reply", "type": "main", "index": 0 }]] } },
"settings": {}
}
]Copy it in, import it, publish it and restart n8n. Per n8n's CLI docs, import:workflow deactivates what it imports, publish:workflow replaces the deprecated update:workflow --active=true from n8n 2.0, and CLI changes take effect only after a restart.
sbx scp hello-webhook.json $id:/home/user/hello-webhook.json
sbx exec $id "docker cp hello-webhook.json n8n:/tmp/hello-webhook.json"
sbx exec $id "docker exec n8n n8n import:workflow --input=/tmp/hello-webhook.json"
sbx exec $id "docker exec n8n n8n publish:workflow --id=helloWebhook01"
sbx exec $id "docker restart n8n"Importing 1 workflows...
Successfully imported 1 workflow.
Publishing workflow with ID: helloWebhook01 (current version)
Note: Changes will not take effect if n8n is running.
…
Finished building workflow dependency index. Processed 0 draft workflows, 1 published workflows.Call the webhook from outside#
On the private port, a caller has to carry the token. We tried three ways:
curl -s -o /dev/null -w "%{http_code}\n" -X POST -H "Content-Type: application/json" \
-d '{"name":"Ada"}' $B/webhook/hello
# 401
curl -s -X POST -H "Content-Type: application/json" \
-d '{"name":"Grace"}' "$B/webhook/hello?_token=<token>"
# {"greeting":"hello Grace","receivedAt":"2026-10-27T09:44:46.401Z"} (200)
curl -s -b jar.txt -X POST -H "Content-Type: application/json" \
-d '{"name":"Ada"}' $B/webhook/hello
# {"greeting":"hello Ada","receivedAt":"2026-10-27T09:44:39.365Z"} (200)A POST with ?_token= in the URL went straight through to n8n with a 200 and also set the cookie. (A GET with the token gets a 302 first, as above.) So a sender that lets you paste a full URL can call a private port. Then we made the port public:
sbx host $id 5678 --public --title n8n
curl -s -X POST -H "Content-Type: application/json" -d '{"name":"Linus"}' $B/webhook/hello
# {"greeting":"hello Linus","receivedAt":"2026-10-27T09:45:09.256Z"}
curl -s $B/rest/workflows
# {"status":"error","message":"Unauthorized"}
curl -s -X POST $B/webhook/nope
# {"code":404,"message":"The requested webhook \"POST nope\" is not registered.", …}With the port public, anyone can reach the editor's sign-in page and every webhook path. n8n's own login still guards the editor and API.
Private or public: which to pick#
| Private port (default) | Public port | |
|---|---|---|
| Webhook URL you give the sender | https://…-5678.box.sandboxer.com/webhook/hello?_token=<token> |
https://…-5678.box.sandboxer.com/webhook/hello |
| Request without the token | 401 from Sandboxer, n8n never sees it |
Reaches n8n |
| Who can reach the editor | Only people with the token, then n8n's login | Anyone, then n8n's login |
| What the token gives away | The whole port, editor included: a sender that stores your webhook URL holds it | Nothing to give away |
| Senders that reject query strings | Can't call it | Work |
| Protect the webhook itself | The token already does | Use the Webhook node's Basic, Header or JWT auth (n8n docs) |
Keep the port private while you set up the owner account and build workflows. If you only test with your own scripts, stay private. Make it public when an outside service has to call in and can't carry the token, and turn on authentication in each Webhook node. Switching public to private later mints a new token: in our run the old link returned 401 and the new one 302. Update the URL anywhere you pasted the old one.
What survives stop and resume#
We noted n8n's container PID (2013) and ran:
sbx stop $id --wait # 2.2 s
sbx resume $id # 1.4 s
sbx info $id # memoryRestored: trueWhile stopped, the webhook URL returned 404. After resume, n8n answered 223 ms after sbx resume returned, with the same PID and start time: the process never restarted.
Memory is restored only when the resume lands on the same CPU type. To see the other case, we stopped again and resumed with --type default, which always boots from disk:
memoryRestored: false, resumeReason: resized
/proc/uptime: 19.66
n8n PID 615, started 2026-10-27T09:45:46Z
{"greeting":"hello after cold boot","receivedAt":"2026-10-27T09:46:04.498Z"}Docker is an enabled service, so it started at boot and --restart unless-stopped brought n8n back. The webhook answered 16.9 seconds after resume returned. The workflow was still published and the owner account still existed (showSetupOnFirstLoad: false), because both live in SQLite on the n8n_data volume. Without the restart policy, n8n stays down after a cold boot until you start it. Hosted routes come back either way. You can move to a bigger size on resume. Moving to a smaller size isn't supported yet (create a new sandbox and copy), so pick small or default up front.
FAQ#
Which size does n8n need?#
small (2 vCPU, 4 GB, $0.018 per hour) ran this test with n8n using 731 MiB at idle. Use default for heavy workflows or many executions at once.
Why does the editor show the wrong webhook URL?#
N8N_WEBHOOK_URL isn't set, or still points at localhost. Set it to https://<subdomain>-5678.box.sandboxer.com/ and recreate the container. In n8n 2.35 and later, WEBHOOK_URL is deprecated.
Does the n8n URL change after stop, resume or resize?#
No. The subdomain and hosted routes are kept. A fork gets a new subdomain and no hosted ports, so set the n8n URL variables again there.
Do I need n8n's tunnel option?#
No. n8n's tunnel is for local development. The hosted URL already reaches the sandbox over HTTPS.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.