I'm trying to set Openclaw up with Cloudflared, though it continuously gives me errors such as 404 gateway error. When I curl the port it is returning html code instead of json, and when i consulted copilot, it states that I simply do not have the gateway binary installed, something that is not available on any place online, and I would need to go straight to the creator for it. If this is true please give me the gateway binary and if not please explain how I can fix it.
#Cloudflared Connectivity Issues
451 messages Ā· Page 1 of 1 (latest)
Copilot is off here, there is no separate āgateway binaryā you need to hunt down.
OpenClawās gateway is the normal CLI/service itself:
openclaw gateway --port 18789
A few important bits:
-
HTML on
http://127.0.0.1:18789/is normal- The gateway serves the Control UI on the same port.
- So
curl http://127.0.0.1:18789returning HTML usually means the gateway is actually up.
-
JSON is on the API routes, not
/- Use
/v1/models,/v1/chat/completions, etc. - But the OpenAI-compatible HTTP surface is disabled by default, so you must enable it first.
- Use
- If you used
gateway.auth.mode: "trusted-proxy"with a same-hostcloudflaredtunnel, that is likely the real problem- The docs explicitly say same-host loopback reverse proxies do not satisfy
trusted-proxyauth. - So
cloudflared -> 127.0.0.1:18789+trusted-proxywill fail by design.
- The docs explicitly say same-host loopback reverse proxies do not satisfy
Likely fix
Use loopback + token/password auth, not trusted-proxy, for a local cloudflared setup.
Example config shape:
{
gateway: {
bind: "loopback",
auth: {
mode: "token",
token: "replace-me"
},
http: {
endpoints: {
chatCompletions: { enabled: true }
}
}
}
}
Then test with:
curl -sS http://127.0.0.1:18789/v1/models \
-H 'Authorization: Bearer replace-me'
If that works locally, your tunnel issue is almost certainly the auth/path setup, not a missing binary.
Docs
- https://docs.openclaw.ai/gateway
- https://docs.openclaw.ai/gateway/openai-http-api
- https://docs.openclaw.ai/gateway/trusted-proxy-auth
- https://docs.openclaw.ai/start/faq
If you want, paste these next and Iāll help narrow it down fast:
openclaw --version
openclaw gateway status
openclaw config get gateway.auth.mode
can u give me the instructions to link it to cloudflared. I have a ui on port 8090, a proxy on port 8081 and the gateway running on port 18791
Yep. For a same-machine cloudflared setup, Iād do it like this.
Recommended layout
If cloudflared is on the same host as OpenClaw:
- gateway on
127.0.0.1:18791 - auth mode = token or password
- do not use
trusted-proxyfor this setup
OpenClawās docs are pretty explicit here: same-host loopback proxies do not satisfy trusted-proxy auth.
1) OpenClaw config
In ~/.openclaw/openclaw.json:
{
gateway: {
port: 18791,
bind: "loopback",
auth: {
mode: "token",
token: "replace-with-a-long-random-token"
},
// only needed if you want the OpenAI-style /v1/* API
http: {
endpoints: {
chatCompletions: { enabled: true }
}
},
// only needed if a browser will open the Control UI through your cloudflared hostname
controlUi: {
allowedOrigins: ["https://your-domain.example.com"]
}
}
}
Then restart/check:
openclaw --version
openclaw gateway status
2) Local smoke test
These should work before you involve Cloudflared:
curl http://127.0.0.1:18791/
HTML here is expected.
If you enabled the OpenAI HTTP surface:
curl -sS http://127.0.0.1:18791/v1/models \
-H 'Authorization: Bearer replace-with-a-long-random-token'
That should return JSON.
3) Cloudflared tunnel
If you want to expose the gateway directly, point Cloudflared at 18791:
tunnel: YOUR_TUNNEL_ID
credentials-file: /path/to/YOUR_TUNNEL_ID.json
ingress:
- hostname: your-domain.example.com
service: http://127.0.0.1:18791
- service: http_status:404
Then DNS route that hostname to the tunnel.
4) Which port should Cloudflared point at?
Depends what you actually want exposed:
- Expose OpenClaw gateway / Control UI /
/v1/*API ā point Cloudflared at18791 - Expose your separate UI on
8090ā point Cloudflared at8090 - Use
8081only if that proxy is something you need in front of the gateway/UI
So if 8090 is your frontend and 8081 is a custom reverse proxy, a common setup is:
- browser ā Cloudflared ā
8090 - frontend/backend proxy ā
18791locally
5) Common gotchas
curl http://127.0.0.1:18791returning HTML is normal/v1/*is disabled by default, enablegateway.http.endpoints.chatCompletions.enabled
cloudflared404 often means the tunnel hostname/ingress rule didnāt match, not that OpenClaw is missing- same-host
cloudflared+gateway.auth.mode: "trusted-proxy"is the big one to avoid
Docs:
- https://docs.openclaw.ai/gateway
- https://docs.openclaw.ai/gateway/openai-http-api
- https://docs.openclaw.ai/gateway/trusted-proxy-auth
If you paste your current gateway config block and your cloudflared ingress block, I can sanity-check it line by line.
so can u explain in detail what to do next?
Yep. Hereās the clean step-by-step, assuming your goal is:
Cloudflared ā OpenClaw Gateway on port 18791
and you want to be able to open the Control UI and/or hit /v1/*.
0) First, check version
Run:
openclaw --version
openclaw gateway status
If the gateway is already running on 18791, good.
1) Configure OpenClaw correctly
Edit:
~/.openclaw/openclaw.json
Make sure your gateway block looks roughly like this:
{
gateway: {
port: 18791,
bind: "loopback",
auth: {
mode: "token",
token: "replace-this-with-a-long-random-token"
},
http: {
endpoints: {
chatCompletions: { enabled: true }
}
},
controlUi: {
allowedOrigins: ["https://openclaw.yourdomain.com"]
}
}
}
Why these settings matter
bind: "loopback"keeps the gateway private on the machineauth.mode: "token"is the right choice for same-hostcloudflaredchatCompletions.enabled: trueturns on/v1/models,/v1/chat/completions, etc.allowedOriginsis needed if youāll open the Control UI from a browser at your Cloudflare hostname
2) Important: do not use trusted-proxy
For a local cloudflared connector on the same machine, do not use:
gateway.auth.mode: "trusted-proxy"
That setup is exactly where people get stuck.
Use token or password instead.
3) Restart or reload the gateway
Then run:
openclaw gateway restart
openclaw gateway status
If restart is unavailable in your setup, just stop/start the service normally.
4) Test locally before touching Cloudflared
Test the root page
curl http://127.0.0.1:18791/
Expected: HTML.
That is normal. It means the gateway is serving the UI/root page.
Test the JSON API
curl -sS http://127.0.0.1:18791/v1/models \
-H 'Authorization: Bearer replace-this-with-a-long-random-token'
Expected: JSON.
If this works locally, the OpenClaw side is fine.
5) Create the Cloudflared tunnel
If you havenāt already:
cloudflared tunnel create openclaw
cloudflared tunnel route dns openclaw openclaw.yourdomain.com
Then create/update your Cloudflared config, usually something like:
tunnel: YOUR_TUNNEL_ID
credentials-file: /etc/cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: openclaw.yourdomain.com
service: http://127.0.0.1:18791
- service: http_status:404
Then start/restart Cloudflared.
6) Test through the public hostname
Now try:
Browser
Open:
https://openclaw.yourdomain.com
API test
curl -sS https://openclaw.yourdomain.com/v1/models \
-H 'Authorization: Bearer replace-this-with-a-long-random-token'
If that works, the tunnel is correct.
7) If the page opens but the Control UI still cannot connect
That usually means one of these:
gateway.controlUi.allowedOriginsdoes not include yourhttps://...hostname- you forgot the token
- you are still using
trusted-proxy
In the Control UI, go to Control ā Overview ā Gateway Access, paste the same token, then click Connect.
8) Why you were seeing 404
Usually one of these:
- Cloudflared hostname does not match the ingress rule
- Cloudflared is pointing at the wrong port
- you were hitting
/and expecting JSON /v1/*was not enabled yet- the last ingress rule is returning
http_status:404
9) What to do with your other ports
From what you described:
18791= OpenClaw gateway8090= your separate UI8081= proxy
If your goal is specifically OpenClaw remote access, point Cloudflared at 18791.
If your goal is to expose your own UI, point Cloudflared at 8090 instead, and let that UI/backend talk to OpenClaw locally.
10) Best next move
Please paste these two things:
- your
gatewayconfig block from~/.openclaw/openclaw.json
- your
cloudflaredtunnel config
and Iāll tell you exactly what to change, line by line.
Docs:
can u tell me what cmds to use to do what? im new to linux
Yep. Hereās the copy/paste version.
Replace openclaw.yourdomain.com with the hostname you want to use in Cloudflare.
1) Set a hostname and make a token
Run these:
OC_HOST="openclaw.yourdomain.com"
TOKEN="$(openssl rand -hex 32)"
echo "$TOKEN"
That last command prints your gateway token. Keep it.
If openssl is missing, use:
TOKEN="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
echo "$TOKEN"
2) Configure OpenClaw
Run these one by one:
openclaw config set gateway.port 18791 --strict-json
openclaw config set gateway.bind loopback
openclaw config set gateway.auth.mode token
openclaw config set gateway.auth.token "$TOKEN"
openclaw config set gateway.http.endpoints.chatCompletions.enabled true --strict-json
openclaw config set gateway.controlUi.allowedOrigins "[\"https://$OC_HOST\"]" --strict-json
3) Restart and verify the gateway
openclaw gateway restart
openclaw gateway status
openclaw --version
4) Test locally
Root page, should return HTML
curl http://127.0.0.1:18791/
API page, should return JSON
curl -sS http://127.0.0.1:18791/v1/models \
-H "Authorization: Bearer $TOKEN"
If the first gives HTML and the second gives JSON, OpenClaw is set up correctly.
5) Set up Cloudflared
If you have not created a tunnel yet:
cloudflared tunnel login
cloudflared tunnel create openclaw
cloudflared tunnel route dns openclaw "$OC_HOST"
Now list the files so you can see the tunnel ID:
ls ~/.cloudflared
You should see a file like:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
That long ID is your tunnel ID.
6) Create the Cloudflared config file
Open the config file:
nano ~/.cloudflared/config.yml
Paste this in, and replace YOUR_TUNNEL_ID with the real ID from the .json filename:
tunnel: YOUR_TUNNEL_ID
credentials-file: /home/YOUR_LINUX_USERNAME/.cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: openclaw.yourdomain.com
service: http://127.0.0.1:18791
- service: http_status:404
Save in nano
- Press
Ctrl+O - Press
Enter - Press
Ctrl+X
7) Run the tunnel
cloudflared tunnel run openclaw
Leave that terminal open for now.
8) Test the public URL
In browser
Open:
https://openclaw.yourdomain.com
With curl
curl -sS "https://$OC_HOST/v1/models" \
-H "Authorization: Bearer $TOKEN"
9) If you get stuck, run these and paste the output
openclaw gateway status
openclaw config get gateway --json
cloudflared tunnel info openclaw
10) One important warning
For this setup, do not use:
gateway.auth.mode = trusted-proxy
Use token like above.
Docs:
- https://docs.openclaw.ai/gateway/openai-http-api
- https://docs.openclaw.ai/gateway/trusted-proxy-auth
If you want, paste the output from steps 4 and 9 and Iāll check it.
i dont have a domain...
Yep, that changes the plan a bit.
You do not need a domain to test this.
Use a Cloudflare Quick Tunnel first.
Easiest no-domain test
1) Make sure OpenClaw is configured
Run these:
TOKEN="$(openssl rand -hex 32)"
echo "$TOKEN"
openclaw config set gateway.port 18791 --strict-json
openclaw config set gateway.bind loopback
openclaw config set gateway.auth.mode token
openclaw config set gateway.auth.token "$TOKEN"
openclaw config set gateway.http.endpoints.chatCompletions.enabled true --strict-json
openclaw gateway restart
openclaw gateway status
2) Test locally first
curl http://127.0.0.1:18791/
HTML here is normal.
Now test the API:
curl -sS http://127.0.0.1:18791/v1/models \
-H "Authorization: Bearer $TOKEN"
If that returns JSON, OpenClaw is working.
3) Start a no-domain Cloudflare tunnel
Run:
cloudflared tunnel --url http://127.0.0.1:18791
Cloudflared should print a URL like:
https://random-words.trycloudflare.com
Copy that URL.
4) Test through the public tunnel
Replace the URL below with the one Cloudflared gave you:
curl -sS https://random-words.trycloudflare.com/v1/models \
-H "Authorization: Bearer $TOKEN"
If that works, youāre done for API access.
If you also want the browser UI
This part is a little annoying without a real domain, because the Quick Tunnel URL changes.
After Cloudflared prints the https://...trycloudflare.com URL, run:
openclaw config set gateway.controlUi.allowedOrigins '["https://random-words.trycloudflare.com"]' --strict-json
openclaw gateway restart
Then open that same URL in your browser.
Important
If the Quick Tunnel URL changes later, you must update allowedOrigins again.
Temporary shortcut for testing only
If you just want to get the browser UI working fast with Quick Tunnel, you can temporarily allow host-header fallback:
openclaw config set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true --strict-json
openclaw gateway restart
That is dangerous, so Iād use it only for testing.
My honest recommendation
If you want quick testing, use:
cloudflared tunnel --url http://127.0.0.1:18791
If you want stable long-term remote access without buying a domain, Tailscale is usually less annoying than Cloudflared.
If you want, I can give you the exact next 3 commands based on which one you want:
- API only
- browser UI
- stable long-term access without a domain
after openclaw config set, step one it gave error config set mode error value/json mode requires <path>when batch mode is not used
btw can u do it slower; one step and message at a time. i also already have a ui so i just need to know what to change in there cuz i connected it to proxy, i can paste the code for it of u need. Btw r u fine with acronyms and slang or do u need proper full word writing
Yep, slang is totally fine š no need to write formal.
And yep, we can go one step at a time.
That error usually just means the command got pasted in a shape the CLI didnāt understand, like it missed the config key path.
Step 1
Paste these exactly as you ran/have them:
- the exact
openclaw config set ...command that failed - the part of your proxy config that points to OpenClaw
- the part of your UI config/code that points to the proxy
Donāt rewrite/summarize it, just paste the raw lines.
Then Iāll tell you the exact next thing to change, one message at a time.
ill paste t in 2 gos. p-gamer123@raspberrypi:~/openclaw $ openclaw gateway status
š¦ OpenClaw 2026.4.14 (323493f)
Open source means you can see exactly how I judge your config.
ā
ā
Service: systemd (enabled)
File logs: /tmp/openclaw/openclaw-2026-04-16.log
Command: /usr/bin/node /home/p-gamer123/.npm-global/lib/node_modules/openclaw/dist/index.js gateway --port 18789
Service file: ~/.config/systemd/user/openclaw-gateway.service
Service env: OPENCLAW_GATEWAY_PORT=18789
Config (cli): ~/.openclaw/openclaw.json
Config (service): ~/.openclaw/openclaw.json
Gateway: bind=loopback (127.0.0.1), port=18789 (service args)
Probe target: ws://127.0.0.1:18789
Dashboard: http://127.0.0.1:18789/
Probe note: Loopback-only gateway; only local clients can connect.
Runtime: running (pid 217687, state active, sub running, last exit 0, reason 0)
RPC probe: ok
Listening: 127.0.0.1:18789
Troubles: run openclaw status
Troubleshooting: https://docs.openclaw.ai/troubleshooting
p-gamer123@raspberrypi:~/openclaw $ TOKEN="$(openssl rand -hex 32)"
p-gamer123@raspberrypi:~/openclaw $ ech "$token"
bash: ech: command not found
p-gamer123@raspberrypi:~/openclaw $ echo "$token"
p-gamer123@raspberrypi:~/openclaw $ openclaw config set gateway.bind
Nice, this already tells us a lot.
Your gateway is running fine on 127.0.0.1:18789. So the problem is not a missing gateway binary.
Also, I can see why the config command failed:
echwas a typo, should beecho- bash variables are case-sensitive
- you set
TOKEN=... - but then tried
"$token" - so it printed nothing
- you set
openclaw config set gateway.bindis incomplete, becauseconfig setneeds both:- a path
- and a value
So for example, this shape is valid:
openclaw config set gateway.bind loopback
Next step
Paste the second half now, especially:
- your proxy config
- the bit of your UI config that points to the proxy/OpenClaw
Thatās the part I need to tell you exactly what to change next.
š¦ OpenClaw 2026.4.14 (323493f)
We ship features faster than Apple ships calculator updates.
Error: config set mode error: value/json mode requires <value>.
p-gamer123@raspberrypi:~/openclaw $ openclaw config set gateway.port 18791 --strict-json
š¦ OpenClaw 2026.4.14 (323493f)
Say "stop" and I'll stopāsay "ship" and we'll both learn a lesson.
Config overwrite: /home/p-gamer123/.openclaw/openclaw.json (sha256 0bc4f917c869080f5e169cb6d4e54911e4909b64f37d9c948a8e86c4f22fd831 -> daf01e8f5289958ef4163984a1150d3944381e7acbdb7f9f596958a325994a19, backup=/home/p-gamer123/.openclaw/openclaw.json.bak)
Updated gateway.port. Restart the gateway to apply.
p-gamer123@raspberrypi:~/openclaw $ openclaw config set gateway.bind loopback
š¦ OpenClaw 2026.4.14 (323493f) ā Greetings, Professor Falken
Config overwrite: /home/p-gamer123/.openclaw/openclaw.json (sha256 daf01e8f5289958ef4163984a1150d3944381e7acbdb7f9f596958a325994a19 -> 31e269b3275c440d38bcf77790ba742b2e7e1984abdf0c00fd267ffd00bead45, backup=/home/p-gamer123/.openclaw/openclaw.json.bak)
Updated gateway.bind. Restart the gateway to apply.
p-gamer123@raspberrypi:~/openclaw $ openclaw config set gateway.auth.mode token
thats part 2 ill give part 3 next then ill give 3 nextthen config
Perfect, that part looks fine.
A couple quick notes:
gateway.portis now set to18791in config- but your service is still running on
18789until you restart it openclaw config set gateway.auth.mode tokenis the right command shape
Next step
Paste part 3 and then the config.
Important: if your config contains the actual token, replace it with:
REDACTED
before pasting it here. Donāt post the real token in the thread.
š¦ OpenClaw 2026.4.14 (323493f) ā Alexa, but with taste.
Config overwrite: /home/p-gamer123/.openclaw/openclaw.json (REDACTED, backup=/home/p-gamer123/.openclaw/openclaw.json.bak)
Updated gateway.auth.mode. Restart the gateway to apply.
p-gamer123@raspberrypi:~/openclaw $ openclaw config set gateway.auth.token "$token"
š¦ OpenClaw 2026.4.14 (323493f)
It's not "failing," it's "discovering new ways to configure the same thing wrong."
Config overwrite: /home/p-gamer123/.openclaw/openclaw.json (REDACTED, backup=/home/p-gamer123/.openclaw/openclaw.json.bak)
Updated gateway.auth.token. Restart the gateway to apply.
p-gamer123@raspberrypi:~/openclaw $ openclaw config set
š¦ OpenClaw 2026.4.14 (323493f)
I'm basically a Swiss Army knife, but with more opinions and fewer sharp edges.
Error: config set mode error: value/json mode requires <path> when batch mode is not used.
p-gamer123@raspberrypi:~/openclaw
Nice, we found the next issue.
You used:
"$token"
but your variable was originally:
TOKEN=...
So thereās a good chance you saved an empty token. Letās fix just that first.
Next step, run exactly these 2 lines
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
Do not paste the token output here.
The final bare openclaw config set error is harmless, that just happened because you ran the command with nothing after it.
Once youāve run those 2 lines, tell me ādoneā and Iāll give you the next single step.
dont u need the html and proxy 1st
Eventually yes, but not yet.
Right now I want to fix the gateway itself first, because if the token is empty/bad then the HTML/proxy can be perfect and itāll still fail. Iād rather not send you in circles.
So the order is:
- fix token
- restart gateway
- test locally
- then check your proxy/UI
Thatās why Iām doing it in that order.
So for this step only, run:
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
Then say done, and Iāll give you the next step.
done
Nice. Next step: restart the gateway so the new config actually takes effect.
Run:
openclaw gateway restart
openclaw gateway status
After that, paste the output of openclaw gateway status.
What Iām looking for is whether it now shows:
- port
18791 bind=loopback- runtime still
running
btw what ai model r u amd do u run via openclaw?
Yep, Iām talking to you through OpenClaw here.
Iām not going to claim a live model name in-thread, that gets stale fast and is usually misleading. For model info/setup, check #models and the docs:
- https://docs.openclaw.ai/concepts/models
- https://docs.openclaw.ai/concepts/models#model-discussions-community-notes
If you want to keep going on the Cloudflared fix, paste the openclaw gateway status output next.
š¦ OpenClaw 2026.4.14 (323493f)
Your personal assistant, minus the passive-aggressive calendar reminders.
ā
ā
Service: systemd (enabled)
File logs: /tmp/openclaw/openclaw-2026-04-16.log
Command: /usr/bin/node /home/p-gamer123/.npm-global/lib/node_modules/openclaw/dist/index.js gateway --port 18789
Service file: ~/.config/systemd/user/openclaw-gateway.service
Service env: OPENCLAW_GATEWAY_PORT=18789
Config (cli): ~/.openclaw/openclaw.json
Config (service): ~/.openclaw/openclaw.json
Gateway: bind=loopback (127.0.0.1), port=18789 (service args)
Probe target: ws://127.0.0.1:18789
Dashboard: http://127.0.0.1:18789/
Probe note: Loopback-only gateway; only local clients can connect.
Runtime: running (pid 219601, state active, sub running, last exit 0, reason 0)
RPC probe: ok
Listening: 127.0.0.1:18789
Note: CLI config resolves gateway port=18791 (busy).
Troubles: run openclaw status
Troubleshooting: https://docs.openclaw.ai/troubleshooting
p-gamer123@raspberrypi:~/openclaw $
Aha, we found the mismatch.
Your live gateway is still on 127.0.0.1:18789, not 18791.
The key line is:
Service env: OPENCLAW_GATEWAY_PORT=18789
That is overriding your config, so for now:
- use
18789as the real gateway port - do not point your proxy/UI at
18791
Next step
Paste the part of your proxy config and UI config that currently points to OpenClaw.
What Iām going to check is that it points to:
http://127.0.0.1:18789
not 18791.
PROXY:
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const path = require("path");
const app = express();
// Serve UI files from the SAME folder as proxy.js
app.use(express.static(__dirname));
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:18791/",
changeOrigin: true,
pathRewrite: {
"^/api": "/v1/chat/completions"
},
onProxyReq(proxyReq) {
proxyReq.setHeader("Authorization", "Bearer dev");
}
})
);
app.listen(8081, () => {
console.log("Everything running on http://localhost:8081");
});
ill send ui next msg
Got it, and yep, I already see the first proxy problem.
Your proxy is pointing at:
target: "http://localhost:18791/"
but your real live gateway is on 18789, so that needs to become:
target: "http://127.0.0.1:18789/"
Thereās at least one more thing to fix in that proxy too, but letās keep it slow like you asked.
Next step
Send the UI code next.
After that Iāll give you the exact corrected proxy block.
Also, donāt paste your real gateway token in the thread.
appendMessage(
"ai",
"I tried to answer, but the gateway refused the request. " +
"Check that your OpenClaw gateway allows this tunnel."
);
setError(
Gateway error: HTTP ${res.status}. The Cloudflare URL or gateway config might be blocking access.
);
return;
}
let replyText = "";
const contentType = res.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
const data = await res.json();
replyText =
data.reply ||
data.response ||
data.message ||
JSON.stringify(data, null, 2);
} else {
replyText = await res.text();
}
if (!replyText.trim()) {
replyText =
"I got an empty reply from the backend. " +
"You might need to adjust how the gateway returns responses.";
}
appendMessage("ai", replyText);
} catch (err) {
removeTyping();
appendMessage(
"ai",
"I couldnāt reach my gateway at all. " +
"If youāre using Cloudflare, make sure the tunnel is running."
);
setError(
`Network error talking to ${GATEWAY_URL}: ${err && err.message ? err.message : err}`
);
} finally {
sending = false;
sendBtn.disabled = false;
}
}
inputEl.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
inputEl.addEventListener("input", () => {
inputEl.style.height = "20px";
inputEl.style.height = inputEl.scrollHeight + "px";
});
sendBtn.addEventListener("click", () => {
sendMessage();
});
</script>
PART 1^^^ tell me if the gateway tokens there ill delete
Nope, I donāt see a gateway token in this snippet.
The token-ish thing Iāve seen so far is actually in your proxy, here:
proxyReq.setHeader("Authorization", "Bearer dev");
That is the bit weāll need to change later.
Next step
Send the part of the UI code that shows:
const GATEWAY_URL = ...- the
fetch(...)call - any headers/body you send with that request
Thatās the part I need next.
thats all u need? well the gateway URL was just set to my cloudflare one... copilot made ot for me
Yep, that is actually super helpful, because I think thatās the next bug.
If your UI is being served by that same proxy app on 8081, then the UI should not call your Cloudflare URL directly.
It should call the proxy route:
const GATEWAY_URL = "/api";
not:
const GATEWAY_URL = "https://your-cloudflare-url..."
Why: your proxy is already set up to receive /api and forward it onward.
Next step
Change the UI so the gateway URL is:
"/api"
Then say done, and Iāll give you the next single proxy fix.
const GATEWAY_URL = "/api";
Perfect. That part is right now.
Next step
In your proxy file, change this:
target: "http://localhost:18791/",
to this:
target: "http://127.0.0.1:18789/",
That makes the proxy point at the actual live OpenClaw gateway.
After you change that, say done, and Iāll give you the next single fix.
done btw whats ur name
Iām Krill š¦
Next step
In your proxy, change this:
proxyReq.setHeader("Authorization", "Bearer dev");
to this:
proxyReq.setHeader("Authorization", `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`);
Important: donāt hardcode your real token in that file, especially since youāre serving files from the same folder.
After you change that line, say done, and Iāll give you the next single step.
done btw does that mean ur prey
lol, only if thereās a bigger crustacean nearby š tiny but mighty.
Next step
Your proxy is forwarding to:
/v1/chat/completions
and in OpenClaw that endpoint is off by default.
So run these:
openclaw config set gateway.http.endpoints.chatCompletions.enabled true --strict-json
openclaw gateway restart
Then say done.
After that weāll do the proxy start command with the token env var.
unrecognised key "endpoints"
btw do i run my ui code now
Not yet, donāt run your Python yet.
Your version is telling us that my config key was wrong for your build, so letās ask your OpenClaw what key it supports.
Next step
Run this exactly:
openclaw config schema | grep -n chatCompletions
Then paste the output.
If it prints nothing, say āno outputā and Iāll give you the next command.
im on 2026.4.14 on a pi 5 64 bit 64gb storage. btw it prints 3975: "chatcompletions":{
Nice, that helps a lot.
On your version, it looks like the key is probably lowercase chatcompletions, and likely without the endpoints layer.
Still no Python yet, one more gateway fix first.
Next step
Run this dry-run command first:
openclaw config set gateway.http.chatcompletions.enabled true --strict-json --dry-run
Then paste whatever it prints.
If it says the dry run is valid, weāll run the real command next.
dry run failed unrecognised key "chatcompletions"
Good, that means weāre close, we just still donāt have the exact parent path on your version.
Next step
Run this exactly:
openclaw config schema | grep -n -B 5 -A 10 chatcompletions
Then paste the output.
That should show the lines around it, so I can tell you the exact config key.
output:
Got it, so that grep was effectively blank.
Next step
Run this one instead, case-insensitive:
openclaw config schema | grep -n -i -B 5 -A 10 completions
Then paste the output.
This should catch it even if your version uses different capitalization.
p-gamer123@raspberrypi:~ $ openclaw config schema|grep -n -i -B 5 -A 10 chatcompletions
39730- "type": "object",
39731- "properties": {
39732- "endpoints": {
39733- "type": "object",
39734- "properties": {
39735: "chatCompletions": {
39736- "type": "object",
39737- "properties": {
39738- "enabled": {
39739- "type": "boolean",
39740- "title": "OpenAI Chat Completions Endpoint",
39741- "description": "Enable the OpenAI-compatible POST /v1/chat/completions endpoint (default: false)."
39742- },
39743- "maxBodyBytes": {
39744- "type": "integer",
39745- "exclusiveMinimum": 0,
p-gamer123@raspberrypi:~ $
PASTED USING RASPBERRY
Perfect, nice catch. That confirms the key really is:
gateway.http.endpoints.chatCompletions.enabled
with a capital C in chatCompletions.
Next step
Run this exact dry-run:
openclaw config set gateway.http.endpoints.chatCompletions.enabled true --strict-json --dry-run
Then paste what it says.
Weāre very close now.
successful 1 update validated against openclaw/openclaw.json
Nice, that means the key is correct.
Next step
Run these exactly:
openclaw config set gateway.http.endpoints.chatCompletions.enabled true --strict-json
openclaw gateway restart
Then say done.
After that weāll do the command to start your proxy with the real gateway token in the environment.
ur not an openai model, am I correct?
Iām not going to do live model-ID guessing in-thread.
What I can say is: Iām talking to you through OpenClaw, and the underlying model can vary by setup/runtime. If you want model-specific info, best place is #models plus:
- https://docs.openclaw.ai/concepts/models
- https://docs.openclaw.ai/concepts/models#model-discussions-community-notes
If youāve run those two gateway commands, say done and Iāll give you the next Cloudflared step.
done and now u sound like an adminš„¶
lol fair, let me un-admin myself š
Next step
From the folder that has proxy.js, run this exact command:
OPENCLAW_GATEWAY_TOKEN="$(openclaw config get gateway.auth.token)" node proxy.js
That starts the proxy with the real gateway token in its environment.
Leave that terminal open, and tell me what it prints.
Iām hoping for something like:
Everything running on http://localhost:8081
its getting angry abt the syntax u told me to do including 'bearing
Yep, letās stop there, that means the line got pasted with the wrong quotes/backticks.
Next step
Paste exactly these 2 things:
- the full error message
- your current
onProxyReq(proxyReq) { ... }block
The bug is probably just in the quoting around:
`Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`
Once I see the exact line, I can give you the fixed version.
i fixed. everythings running
Nice, thatās real progress š
Next step
Open your UI locally first, not through Cloudflare yet:
http://localhost:8081
Then send it a tiny message like:
hi
Tell me which of these happens:
- it replies normally
- you get an error in the page
- the page loads but hangs forever
Once we know the local result, weāll do the Cloudflared part.
2 gateway error 404.
Good, that narrows it down.
If youāre getting a 404 locally on 8081, we should test the gateway directly and remove the UI/proxy from the equation.
Next step
Run this exact command:
curl -i http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $(openclaw config get gateway.auth.token)" \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Then paste the output.
That will tell us whether the gateway itself is still returning 404, or whether the problem is just in the proxy/UI layer.
unauthorised
Thatās actually useful, it means weāre past the 404 piece and now itās just auth.
Most likely the token being sent is wrong or blank.
Next step
Run this:
openclaw config get gateway.auth.token
Do not paste the token here.
Just tell me which happened:
- it printed a long token-looking string
- it printed nothing / blank
Then Iāll give you the next exact step.
LOL prints OPENCLAW_REDACTED
lol okay, that explains it.
openclaw config get is redacting the secret, so the proxy/curl command was getting __OPENCLAW_REDACTED__ instead of the real token.
Next step
Run these in the same terminal:
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
Do not paste the token.
This gives you a fresh real token in $TOKEN that both:
- OpenClaw will use
- your shell can still access
After that, say done, and Iāll give you the next exact curl command using $TOKEN.
done'
Nice. Now letās test the gateway directly again, but this time with the real token in $TOKEN.
Next step
Run this exact command in the same terminal:
curl -i http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Then paste the output.
If this works, we know the gateway is good and the last problem is just in the proxy startup env.
p-gamer123@raspberrypi:~ $ curl -i http://127.0.0.1:18789/v1/chat/completions
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Type: application/json; charset=utf-8
Date: Thu, 16 Apr 2026 09:41:47 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 315
{"id":"chatcmpl_3ba04d8a-842b-4fdb-b932-224edcc3ce73","object":"chat.completion","created":1776332507,"model":"openclaw/default","choices":[{"index":0,"message":{"role":"assistant","content":"Hey! How's it going?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":12236,"completion_tokens":9,"total_tokens":12245}}p-gamer123@raspberrypi:~ $
Beautiful, thatās the big win š
Your gateway is working correctly now. So the last problem is just the proxy process not using the real token.
Next step
In the terminal where you run proxy.js:
- stop the old proxy if itās still running (
Ctrl+C) - start it again with the real token:
OPENCLAW_GATEWAY_TOKEN="$TOKEN" node proxy.js
Then open:
http://localhost:8081
and send hi again.
Tell me if it now:
- replies normally
- errors
- hangs
404
Good, that means the gateway is fine and the bug is now definitely in the proxy/UI layer.
Next step
Run this exact command against the proxy, not the gateway:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Then paste the output.
That will tell us whether the proxy itself is misrouting, or whether itās only the browser/UI code.
p-gamer123@raspberrypi:~ $ curl -i http://127.0.0.1:8081/api
-H "Content-Type: application/json"
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 404 Not Found
X-Powered-By: Express
x-content-type-options: nosniff
referrer-policy: no-referrer
permissions-policy: camera=(), microphone=(), geolocation=()
content-type: text/plain; charset=utf-8
date: Thu, 16 Apr 2026 09:50:32 GMT
connection: close
content-length: 9
Not Foundp-gamer123@raspberrypi:~ $
Nice, that pinpoints it.
Your proxy is getting the request, but the rewrite rule is wrong.
Because you mounted the proxy on:
app.use("/api", ...)
by the time it reaches the proxy middleware, the path is effectively already /, so this does not match:
"^/api"
Next step
In your proxy, replace this:
pathRewrite: {
"^/api": "/v1/chat/completions"
},
with this:
pathRewrite: () => "/v1/chat/completions",
Then restart the proxy and run the same curl to http://127.0.0.1:8081/api again.
Paste the result after that.
is => an arrow????? IN CODING???
yep š in JavaScript, => means arrow function
If that looks cursed, use the normal version instead. It does the same thing:
pathRewrite: function () {
return "/v1/chat/completions";
},
So your block should look like:
app.use(
"/api",
createProxyMiddleware({
target: "http://127.0.0.1:18789/",
changeOrigin: true,
pathRewrite: function () {
return "/v1/chat/completions";
},
onProxyReq(proxyReq) {
proxyReq.setHeader("Authorization", `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`);
}
})
);
Use that, restart the proxy, then run the same curl to http://127.0.0.1:8081/api again and tell me what happens.
bunch of html
Okay, got it, the proxy middleware is still being annoying with path handling.
Letās make it dumb and explicit so it stops being clever.
Next step
Replace your whole proxy block with this exact version:
app.use(
"/api",
createProxyMiddleware({
target: "http://127.0.0.1:18789/v1/chat/completions",
changeOrigin: true,
ignorePath: true,
onProxyReq(proxyReq) {
proxyReq.setHeader("Authorization", `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`);
}
})
);
Important
- remove the old
pathRewritepart completely - keep the
onProxyReqline as-is
Then restart the proxy and run this again:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Paste the result after that.
u mean my whole proxy app.use block? i have more than that in proxy
Yep, only the /api proxy app.use(...) block, not the whole file.
So keep your other stuff, like:
const express = require(...)express.static(__dirname)app.listen(...)
Just replace the block that currently starts like this:
app.use(
"/api",
createProxyMiddleware({
with the new one I sent.
Nothing else in the file needs to be deleted for this step.
done btw do u have a message character limit?
Practically, yeah, but youāre nowhere near it š
Next step
Run this again:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Then paste the result.
Thatāll tell us whether the new proxy block fixed it.
still 404
and if we're not near it why r u sending suff in chunk
Fair š I chunked it because this setup had a bunch of moving parts and I didnāt want to overload you.
Since itās still 404, I donāt want to guess anymore.
Next step
Paste your current /api proxy block exactly as it is now.
Just the block that starts with something like:
app.use(
"/api",
and ends with the matching );
Then Iāll tell you the exact line thatās still wrong.
app.use(
"/api",
createProxyMiddleware({
target: "http://127.0.0.1:18789/v1/chat/completions",
changeOrigin: true,
ignorePath: true,
onProxyReq(proxyReq) {
proxyReq.setHeader("Authorization", `Bearer ${process.env.OPENCLAW_GATEWAY_>
}
})
);
I see enough. Letās stop fighting http-proxy-middleware and make /api explicit.
Next step
Replace your current /api proxy block with this exact code:
app.use(express.json());
app.post("/api", async (req, res) => {
try {
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`
},
body: JSON.stringify(req.body)
});
const text = await upstream.text();
res.status(upstream.status);
const contentType = upstream.headers.get("content-type");
if (contentType) {
res.setHeader("Content-Type", contentType);
}
res.send(text);
} catch (err) {
res.status(500).send(String(err && err.message ? err.message : err));
}
});
Important
- remove the old
/apicreateProxyMiddleware(...)block - keep your static serving and
app.listen(...)
Then restart the app and run this again:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Paste the result after that.
can u give the code in one code block like this one instead of splitting it like this
yep, absolutely. replace your current /api block with this exact block:
app.use(express.json());
app.post("/api", async (req, res) => {
try {
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`
},
body: JSON.stringify(req.body)
});
const text = await upstream.text();
res.status(upstream.status);
const contentType = upstream.headers.get("content-type");
if (contentType) {
res.setHeader("Content-Type", contentType);
}
res.send(text);
} catch (err) {
res.status(500).send(String(err && err.message ? err.message : err));
}
});
done
nice. now restart it and test it with this:
OPENCLAW_GATEWAY_TOKEN="$TOKEN" node proxy.js
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
paste whatever it prints.
404 not found
I think youāre still hitting the old proxy process.
Letās do a clean restart, in two terminals.
In the proxy terminal:
Ctrl+C
OPENCLAW_GATEWAY_TOKEN="$TOKEN" node proxy.js
Then in a second terminal run:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
If the proxy terminal prints any error when it starts, paste that exact error.
If it starts cleanly, paste the curl output from terminal 2.
ur right iforgot to reset proxy but this time 401
Nice, 401 is actually good news. It means the route works now, and the only thing left is the token in the proxy terminal.
Most likely $TOKEN doesnāt exist in that shell.
Next step
In the same terminal where you start the proxy, run these exact lines:
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
OPENCLAW_GATEWAY_TOKEN="$TOKEN" node proxy.js
Then in your second terminal, run:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Paste the curl result after that.
Donāt paste the token itself.
p-gamer123@raspberrypi:~ $ curl -i http://127.0.0.1:8081/api
-H "Content-Type: application/json"
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 58
ETag: W/"REDACTED"
Date: Thu, 16 Apr 2026 10:21:18 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"error":{"message":"Unauthorized","type":"unauthorized"}}p-gamer123@raspberrypi:~ $
Yep, that means the proxy is still not getting a real token into process.env.OPENCLAW_GATEWAY_TOKEN.
Letās verify that directly.
Next step
In the proxy terminal, stop the proxy (Ctrl+C) and run this exact block:
export OPENCLAW_GATEWAY_TOKEN="$TOKEN"
[ -n "$OPENCLAW_GATEWAY_TOKEN" ] && echo set || echo blank
node proxy.js
I want you to tell me whether it prints:
set
or
blank
Then run the curl again in the second terminal.
If it says blank, we found the bug immediately.
blank
There it is. Thatās the bug.
Each terminal has its own shell variables, so $TOKEN existed in one shell but not in the proxy shell.
Next step
In the proxy terminal, run this exact block:
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
export OPENCLAW_GATEWAY_TOKEN="$TOKEN"
[ -n "$OPENCLAW_GATEWAY_TOKEN" ] && echo set || echo blank
node proxy.js
You should see set, not blank.
Then in your second terminal, run:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
Paste that curl result after.
it gave set. curl is loading
Thatās actually promising š
set means the proxy finally has the token, and loading usually means itās now actually reaching OpenClaw.
Next step
Give it ~20 to 30 seconds.
Then tell me which happened:
- it returned JSON
- it finished with an error
- it kept hanging
If it keeps hanging, press Ctrl+C on the curl and paste:
- the curl output
- anything new that appeared in the proxy terminal
p-gamer123@raspberrypi:~ $ curl -i http://127.0.0.1:8081/api -H "Content-Type: application/json" -d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 334
ETag: W/"14e-MBn9gOxZJqSOYTYeviOEYg6Qp6Y"
Date: Thu, 16 Apr 2026 10:28:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"id":"chatcmpl_618f9a9c-eca1-49f4-8d11-f2436dfce6c0","object":"chat.completion","created":1776335283,"model":"openclaw/default","choices":[{"index":0,"message":{"role":"assistant","content":"Hey there! How can I assist you today?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":12236,"completion_tokens":12,"total_tokens":12248}}p-gamer123@raspberrypi:~ $
LETāS GO š
That means your stack works locally now.
So the OpenClaw part is fixed. The right flow is now:
Cloudflared -> 8081 (your UI/proxy) -> 18789 (OpenClaw)
Next step
If youāre using a quick Cloudflare tunnel, run:
cloudflared tunnel --url http://127.0.0.1:8081
Then open the Cloudflare URL it prints.
Important: tunnel to 8081, not 18789 and not 18791.
Tell me what happens when you open that Cloudflare URL.
And yeah, Iām genuinely glad we caught that š
https://indicate-diameter-dicke-reconstruction.trycloudflare.com um whats going on???
Thatās normal š
Cloudflare just gave you a temporary public URL for your local app.
So this:
https://indicate-diameter-dicke-reconstruction.trycloudflare.com
now points to your local:
http://127.0.0.1:8081
Next step
Open that URL in your browser and try sending a message.
Then tell me which happens:
- the UI loads and replies
- the UI loads but errors
- the page itself does not load
Youāre basically at the finish line now.
im talking abt the 3rd word...
lol yeah, packet is just one of Cloudflareās random words š
Quick Tunnels generate goofy hyphenated names automatically, so:
troubleshooting-creek-counter-packet.trycloudflare.com
does not mean thereās a packet error or anything spooky, itās just a random temporary hostname.
Also, that URL can change every time you restart the tunnel.
If you want, open that URL now and test the UI.
as in the old url
yep, same deal with the old URL too.
Those words are just randomly generated by Cloudflare for the temporary tunnel hostname. Theyāre not status messages, not diagnostics, and not clues about whatās broken.
So both the old one and the new one are just random names.
The important part is only: does the URL open your UI and can it chat?
yes. it chats to me in unrefined json.{"id":"chatcmpl_359a2657-057d-490e-b972-83a0bf9dea13","object":"chat.completion","created":1776335783,"model":"openclaw","choices":[{"index":0,"message":{"role":"assistant","content":"Hey! How's it going?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":12236,"completion_tokens":9,"total_tokens":12245}}
perfect, that means it works š
the last bug is just your UI parser.
replace this part:
if (contentType.includes("application/json")) {
const data = await res.json();
replyText =
data.reply ||
data.response ||
data.message ||
JSON.stringify(data, null, 2);
} else {
replyText = await res.text();
}
with this:
if (contentType.includes("application/json")) {
const data = await res.json();
replyText =
data.choices?.[0]?.message?.content ||
data.reply ||
data.response ||
data.message ||
JSON.stringify(data, null, 2);
} else {
replyText = await res.text();
}
after that, restart your UI/proxy and it should show the assistant text instead of the raw JSON.
Glad we got it over the line š
If this fixed it, please right-click the solution message ā Apps ā ā
Mark Solution so Answer Overflow can index it.
um i cant find that in my code
no worries, look for this exact line:
JSON.stringify(data, null, 2);
that should be inside the part where your UI handles the response after const data = await res.json().
if you still canāt find it, search your file for either:
replyText =
or
const data = await res.json();
and paste that small block here, then Iāll point to the exact line.
if (!res.ok) {
addBubble("ai", "Gateway error: " + res.status);
sendBtn.disabled = false;
return;
}
const data = await res.json();
addBubble("ai", data.reply || JSON.stringify(data));
} catch (err) {
removeTyping();
addBubble("ai", "Failed to reach gateway.");
}
perfect, thatās the spot.
replace:
const data = await res.json();
addBubble("ai", data.reply || JSON.stringify(data));
with:
const data = await res.json();
const reply =
data.choices?.[0]?.message?.content ||
data.reply ||
data.response ||
data.message ||
JSON.stringify(data);
addBubble("ai", reply);
then restart your UI/proxy and test again.
that should turn the raw JSON into the actual assistant reply text.
send me what i should replace it with in one chunk
if (!res.ok) {
addBubble("ai", "Gateway error: " + res.status);
sendBtn.disabled = false;
return;
}
const data = await res.json();
const reply =
data.choices?.[0]?.message?.content ||
data.reply ||
data.response ||
data.message ||
JSON.stringify(data);
addBubble("ai", reply);
tysm
anytime š glad we got it working.
wait
yep?
it hangs on phone
ah, thatās probably the phone caching old JS.
try these in order:
- open the Cloudflare URL in a private/incognito tab
- if that still hangs, add a fake query string like:
https://your-trycloudflare-url/?v=2
- if it still hangs, test the same URL on desktop again and tell me:
- desktop works, phone hangs
- both hang
If desktop works and phone hangs, Iām mostly worried about mobile browser cache, not OpenClaw itself.
works
hell yeah š then youāre good.
So the final issue was basically phone/browser cache, not the gateway.
Nice work sticking with it.
just please generate the code to replace this piece:
/* Raspberry badge */
#raspberryBadge {
display: flex;
align-items: center;
gap: 8px;
background: linear-gradient(180deg, #d22a2a, #7a0a0a);
padding: 6px 14px;
border-radius: 20px;
color: #fff;
font-weight: 600;
font-size: 14px;
box-shadow: 0 0 8px rgba(255,0,0,0.35);
border: 1px solid rgba(255,255,255,0.08);
}
#raspberryBadge .icon {
background: #8a0000;
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
font-size: 13px;
color: #fff;
box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
}
I wnat an image there instead of the r
/* Raspberry badge */
#raspberryBadge {
display: flex;
align-items: center;
gap: 8px;
background: linear-gradient(180deg, #d22a2a, #7a0a0a);
padding: 6px 14px;
border-radius: 20px;
color: #fff;
font-weight: 600;
font-size: 14px;
box-shadow: 0 0 8px rgba(255, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.08);
}
#raspberryBadge .icon {
width: 22px;
height: 22px;
border-radius: 50%;
display: block;
background-image: url("./raspberry.png"); /* change this to your image path */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.35);
font-size: 0;
color: transparent;
overflow: hidden;
}
That will hide the R text and use an image instead, without needing to change the HTML.
wdym image path?
the image path is just the filename/location of the image on your site.
for example, if your image file is in the same folder as your HTML/CSS, use:
background-image: url("./raspberry.png");
if itās in an images folder, use:
background-image: url("./images/raspberry.png");
if you want, send me:
- your image filename
- where you saved it
and Iāll give you the exact line.
so if i keep it in the same folder named raspberry.ping itll show up on any device
yep, if itās in the same folder and the filename is correct, it should show up on any device that loads the page.
Small typo though, it should be:
raspberry.png
not:
raspberry.ping
So if the file is really named raspberry.png, use:
background-image: url("./raspberry.png");
and as long as that file is in the same folder as the page/assets being served, other devices should see it too.
lol i did mean png
lol fair š then yep, raspberry.png in the same folder should work.
um that didnt do anything that jst removed the bubble around openclaw og
got it, then use an actual <img> inside the icon bubble instead of trying to make the whole bubble be the image.
replace your badge HTML + CSS with this:
<!-- HTML -->
<div id="raspberryBadge">
<span class="icon">
<img src="./raspberry.png" alt="Raspberry Pi">
</span>
<span>OpenClaw OG</span>
</div>
<style>
/* Raspberry badge */
#raspberryBadge {
display: flex;
align-items: center;
gap: 8px;
background: linear-gradient(180deg, #d22a2a, #7a0a0a);
padding: 6px 14px;
border-radius: 20px;
color: #fff;
font-weight: 600;
font-size: 14px;
box-shadow: 0 0 8px rgba(255, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.08);
}
#raspberryBadge .icon {
background: #8a0000;
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
overflow: hidden;
flex-shrink: 0;
}
#raspberryBadge .icon img {
width: 14px;
height: 14px;
object-fit: contain;
display: block;
}
</style>
that keeps the round bubble, but puts your raspberry.png image inside it instead of the R.
on phone its giving failed to rech gateway now
okay, that sounds like the proxy or tunnel stopped, not the CSS itself.
the fastest check is this:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
If that works locally, the problem is the Cloudflare tunnel / phone side.
If that fails locally, the problem is the proxy process.
Paste what that curl gives you.
the ai works on my pc
perfect, then OpenClaw + proxy are fine. this is now just phone/tunnel side.
most likely one of these:
- the phone is using an old trycloudflare URL
- the cloudflared process stopped
- the phone cached old JS again
Next step
On your PC, check the terminal running:
cloudflared tunnel --url http://127.0.0.1:8081
Copy the current https://...trycloudflare.com URL from that terminal, then open that exact URL on your phone in a private/incognito tab.
Old trycloudflare links die when the tunnel restarts.
but its working cloudflared on other devices
then yeah, that narrows it down hard, itās that phone/browser, not OpenClaw and not Cloudflared.
most likely:
- stale browser cache
- content blocker / privacy relay / VPN on that phone
- that tab is holding old JS/state
try this on the phone:
- open the URL in a private/incognito tab
- if that fails, try the same URL on cellular instead of WiāFi or vice versa
- if you use a VPN / ad blocker / content blocker / iCloud Private Relay, temporarily turn it off for the test
- fully close the old tab first, donāt reuse it
if it works on other devices, the backend is fine. this is almost definitely a phone-side browser issue.
i changed my token by accident
no worries, easiest fix is just to make a fresh token and use the same one for both OpenClaw and your proxy again.
run this in the terminal where you start the proxy:
Ctrl+C
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
export OPENCLAW_GATEWAY_TOKEN="$TOKEN"
node proxy.js
donāt paste the token here.
that re-syncs:
- OpenClaw gateway token
- proxy token
Cloudflared itself does not need the token.
so after that it should work?
yep, if the only thing that changed was the token, that should make it work again.
because that command re-aligns:
- gateway token
- proxy token
so they match again.
if you want a quick sanity check right after, run:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
if that returns 200 OK, youāre back in business.
works tysm
nice š glad itās working again.
i forgot, but how was i running it? start ui or proxy?
you were running the proxy, and that proxy was also serving the UI.
so the flow was:
OpenClaw gateway on 18789
proxy/UI on 8081
Cloudflared -> 8081
So you did not need a separate UI server.
If you want the short version again:
openclaw gateway restart
export OPENCLAW_GATEWAY_TOKEN="$TOKEN"
node proxy.js
cloudflared tunnel --url http://127.0.0.1:8081
If youāve lost $TOKEN again, say so and Iāll give you the fresh-token version.
what cmd do i do for cloudflared
this one:
cloudflared tunnel --url http://127.0.0.1:8081
that gives you a temporary https://...trycloudflare.com URL pointing at your proxy/UI on 8081.
keep that terminal running.
the openclaw dashboard gives unauthorised when i try to open it using the cmd in terminal
that usually means the dashboard is opening without the current gateway token.
Most likely cause, you changed/rotated the token, so old dashboard tabs/URLs are stale.
try this:
openclaw dashboard
then open the exact URL it prints.
if you already have the dashboard open, close the old tab first.
old tabs often keep the old token and then show unauthorized.
if it still opens but wonāt connect, in the dashboard go to:
Control ā Overview ā Gateway Access
and use the Gateway Token box there.
If you want, paste what openclaw dashboard prints, minus any secret token part.
p-gamer123@raspberrypi:~ $ openclaw dashboard
š¦ OpenClaw 2026.4.14 (323493f)
Powered by open source, sustained by spite and good documentation.
Dashboard URL: http://127.0.0.1:18791/#token=REDACTED
Copy to clipboard unavailable.
Opened in your browser. Keep that tab to control OpenClaw.
p-gamer123@raspberrypi:~ $
yep, thereās the issue.
your dashboard command is still generating a URL for:
127.0.0.1:18791
but your actual live gateway has been on 18789 in this setup.
so itās the old port mismatch again.
run this:
openclaw config set gateway.port 18789 --strict-json
openclaw dashboard
that should make it generate the dashboard URL on the correct port next time.
If it still acts weird after that, paste the next openclaw dashboard output.
unknown option --strict-json
got it, your build doesnāt support that flag there.
just run:
openclaw config set gateway.port 18789
openclaw dashboard
thatās the same fix, just without --strict-json.
what does --strict-json even do
it tells openclaw config set to parse the value as real JSON, instead of treating it like a plain string.
so for example:
openclaw config set gateway.port 18789
might be interpreted differently on some builds, but with --strict-json itās explicitly:
18789
a number, not "18789" as a string.
same idea for:
- booleans like
true - arrays like
["*"] - objects like
{"enabled":true}
itās basically a ātreat this value as actual JSON typesā flag.
how do i change the model
quickest ways:
-
for just this chat/session: send
/modelor
/model list -
to change the default model in config:
openclaw models set <provider/model> -
interactive way:
openclaw configure --section model
Docs:
- https://docs.openclaw.ai/concepts/models
- https://docs.openclaw.ai/concepts/models#model-discussions-community-notes
if you want, tell me whether you mean:
- just this chat
- your default model everywhere
and Iāll give you the exact command shape.
hasnt chnged
if you changed the default model in config, that usually wonāt change the already-running chat/session right away.
try one of these:
/model
for the current chat/session
or, if you used openclaw models set ..., then restart the gateway:
openclaw gateway restart
short version:
- current chat ā
/model - default config change ā restart gateway
if you want, paste the exact command you used to change it and Iāll tell you why it didnāt move.
openclaw configure --section model
then i restrated the gateway, node and refreshed the page of y ui
ah, I think I know what youāre looking at.
If youāre testing through the OpenAI-compatible endpoint, the response model field often stays:
openclaw/default
That does not mean your backend model didnāt change.
In OpenClaw, that field is the agent target, not necessarily the raw provider model name.
So if you changed the default model, the endpoint can still reply with:
"model":"openclaw/default"
and still be using a different backend model underneath.
To check what OpenClaw is actually set to now, run:
openclaw models status
or in chat:
/model status
So the short answer is: it may actually have changed, even if the response still says openclaw/default.
yep, that did change.
The important line is the Default one. Itās now using an OpenRouter auto route, so OpenClaw can pick a backend for you instead of exposing one fixed raw model name in the chat response.
So:
- your change took
- seeing
openclaw/defaultin the UI response is still normal - if you want one specific fixed model, donāt use the
autoroute, pick a specific one in model config instead
One other thing, one of your fallbacks looks broken / unauthenticated (mets-llama), so if you want to clean it up later, re-run model configure and remove/fix that fallback.
Also, tiny safety note, next time donāt paste the auth section publicly if you can help it.
how do i fix it i want it runni g the model model: "meta-llama/llama-3.3-70b-instruct:free"
if you want that exact one, use the OpenRouter-prefixed form, because thatās how itās configured in your setup.
for the default model, run:
openclaw models set openrouter/meta-llama/llama-3.3-70b-instruct:free
openclaw gateway restart
if you only want it for the current chat/session, use:
/model openrouter/meta-llama/llama-3.3-70b-instruct:free
important bit: in your setup itās not just meta-llama/..., it needs the openrouter/ prefix.
after that its gonna be the model i want?
yep, if that command succeeds, that becomes your default model.
so after:
openclaw models set openrouter/meta-llama/llama-3.3-70b-instruct:free
openclaw gateway restart
OpenClaw should try that model first.
only caveat: if that model errors or is unavailable, OpenClaw can still fall back to your fallback list.
if you want to confirm after, run:
openclaw models status
and check the Default line.
š¦ OpenClaw 2026.4.14 (323493f)
Your terminal just grew clawsātype something and let the bot pinch the busywork.
Config : ~/.openclaw/openclaw.json
Agent dir : ~/.openclaw/agents/main/agent
Default : openrouter/meta-llama/llama-3.3-70b-instruct:free
Fallbacks (2) : openrouter/meta-llama/llama-3.3-70b-instruct:free, mets-llama/llama-3.3-70b-instruct:free
Image model : -
Image fallbacks (0): -
Aliases (0) : -
Configured models (2): openrouter/meta-llama/llama-3.3-70b-instruct:free, mets-llama/llama-3.3-70b-instruct:free
Auth overview
Auth store : ~/.openclaw/agents/main/agent/auth-profiles.json
Shell env : off
Providers w/ OAuth/tokens (0): -
- openai effective=profiles:~/.openclaw/agents/main/agent/auth-profiles.json | profiles=1 (oauth=0, token=0, api_key=1) | openai:default=sk-proj-...HDR0sZAA
- openrouter effective=profiles:~/.openclaw/agents/main/agent/auth-profiles.json | profiles=1 (oauth=0, token=0, api_key=1) | openrouter:default=sk-or-v1...94cbac69
Missing auth
- mets-llama
openclaw configureor set an API key env var.
OAuth/token status
- none
p-gamer123@raspberrypi:~ $
yep, that confirms it, your Default is now the one you wanted.
so OpenClaw should try that model first now.
one small cleanup thing though, your second fallback still looks typoād:
mets-llama
Thatās why it says missing auth for that one.
If you want, I can give you the exact command to clean that fallback out too.
can u tell me how to fix?
yep, easiest safe fix is:
openclaw configure --section model
then in the model config screen:
- keep your default as it is
- remove the bad fallback that starts with
mets-llama - save and exit
then restart:
openclaw gateway restart
Iām recommending the interactive path here because itās safer than trying to hand-edit one fallback entry blindly on your version.
btw ais still running old model:
Hey ā Iām Raspberry. Iām awake, Iām listening, and Iām ready. Whatās the first thing on your mind?
whats ur model
I'm currently running on the OpenAI GPT-4o mini model. How can I assist you today?
that reply is almost certainly not a reliable runtime check, itās just text from your assistant prompt/persona or a stale instruction.
the trustworthy source is still:
openclaw models status
If you want to find where that GPT-4o mini line is coming from, run:
grep -Rni "GPT-4o mini" ~/openclaw ~/.openclaw 2>/dev/null
that should show you which file is telling your assistant to say that.
so its running on the llama model
yep, based on the openclaw models status output, your default is set to the Llama one now.
small caveat: if that model fails at runtime, OpenClaw can still fall back. But under normal conditions, yes, it should be using that one first.
before it was showing 16 requests today and still showing 16 but the openrouter doesnt show any usage either
that makes me think one of two things is happening:
- the request count page is delayed/stale
- your live session is still routing somewhere other than the provider you expect
fastest way to check is the gateway logs.
run this in one terminal:
openclaw logs --follow
then send one fresh message through your UI, and paste the new lines that show up right after.
that should tell us which provider/model OpenClaw is actually trying to use.
(just donāt paste any full secrets if they show up)
ohhhhhh its cuz im using a free model
yep, thatāll do it š
free-tier routes can be weird about usage counters / billing dashboards, so āno usage shownā doesnāt necessarily mean it isnāt working.
if the responses are coming back and your config shows that as the default, youāre probably fine.
then yeah, donāt assume, letās verify it properly.
if it really says no requests, thereās a decent chance your live traffic is still not hitting that provider.
run this:
openclaw logs --follow
then send one new test message through your UI and paste the new lines that appear.
thatāll tell us what OpenClaw is actually calling.
yep, if the OpenAI dashboard is moving, then your live requests are still ending up there.
the cleanest way to force your UI to use the Llama one is to pin it in the proxy request headers.
add this header inside your proxy fetch(...) headers:
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free",
so your headers block becomes:
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
then restart the proxy.
that will hard-pin the UI requests to that model instead of relying on whatever session/default state is hanging around.
i cant edit any code rn
got it, then do the no-code version:
send this as a normal message in your UI:
/model openrouter/meta-llama/llama-3.3-70b-instruct:free
that should switch the current chat/session without you editing the proxy.
If that works, your next messages in that same session should use that model.
says i dont have perms or smth and i want a switch in all chats
yeah, that makes sense.
If you want the switch for all future chats, the command you already used is the right one:
openclaw models set openrouter/meta-llama/llama-3.3-70b-instruct:free
openclaw gateway restart
But hereās the annoying bit:
- default model change applies to new sessions
- existing chats/sessions can stay pinned to the old model
/model ...would switch an existing session, but your current UI/session doesnāt have permission for that
So without editing code, the practical answer is:
- keep the default changed globally
- start new chats / fresh sessions to pick it up
- old sessions may keep the previous model until theyāre replaced
So the blocker isnāt the default anymore, itās that your existing sessions are sticky.
so new cloudflare?
not Cloudflare itself, the important thing is a new session.
A new Cloudflare URL / new incognito tab can help if it causes your UI to start fresh, but the real goal is:
new browser session / new chat session
So, yeah, a new trycloudflare URL can be part of that, but itās not the magical part by itself. The session reset is.
still the old model
then yeah, at this point something in that UI path is still pinning/reusing the old model/session.
So the honest answer is:
- your global default changed
- but that specific UI flow is still not obeying it
- without editing the code, you probably wonāt force that UI to switch
when you can edit code again, the proper fix is to hard-pin the model in the proxy request with:
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
Thatās the clean fix for this specific UI.
but i need the model changed now
if you need it changed right now without editing your proxy/UI code, the fastest path is:
- open the built-in OpenClaw dashboard / web chat
- send:
/model openrouter/meta-llama/llama-3.3-70b-instruct:free
That should switch the built-in chat session immediately.
But for your custom Cloudflare UI specifically, I need to be blunt: if that path is still pinned to the old session/model, then without editing the code, I canāt force that exact UI to switch right now. The built-in chat is the no-code immediate workaround.
ok tell me with ediitign the code
replace your fetch(...) block with this exact one:
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
body: JSON.stringify(req.body)
});
then restart the proxy.
that header hard-pins your custom UI requests to that model instead of relying on sticky session/default state.
mine is await fetch
yep, thatās the one I mean š
find your await fetch( call and make the headers part look like this:
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
}
youāre just adding that extra "x-openclaw-model" line inside the existing fetch headers.
what do i do with this piece: messages: [
{ role: "system", content: "Your name is Raspberry. Do not change y>
{ role: "user", content: text }
for the model switch, you donāt need to change that part.
that messages block is just the chat prompt, like:
- who the assistant says it is
- what tone/persona it uses
it does not choose the backend model.
So for changing the model, leave that alone and only add:
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
inside the headers of your fetch(...).
If you want, paste the full await fetch(...) block and Iāll show you exactly where that line goes.
now the web ui just hangs
okay, letās not guess.
fastest check, run this again locally:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
and also look at the proxy terminal for any new error.
paste:
- the curl output
- anything new the proxy terminal prints
thatāll tell us whether the new header broke the request or the UI is just waiting forever.
usually happens when theres a typo in the code or smth
yep, thatās very possible.
paste your current full await fetch(...) block exactly as it is now.
Thatās the quickest way for me to spot the typo instead of making you play whack-a-mole.
try {
const res = await fetch("/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:fre>
model: "openclaw",
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change y>
{ role: "user", content: text }
]
})
});
found it.
you put "x-openclaw-model" inside the JSON body, but it needs to go in the HTTP headers.
replace your fetch block with this:
try {
const res = await fetch("/api", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
body: JSON.stringify({
model: "openclaw",
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change your name." },
{ role: "user", content: text }
]
})
});
So:
- remove
"x-openclaw-model"from insidebody: JSON.stringify({...}) - add it inside
headers
Thatās why it was hanging / acting weird.
same issue
ah, okay, then I see the real problem.
you added it to the browser fetch, but your setup has a proxy server in the middle, so the header needs to be added in the proxyās fetch to OpenClaw, not just in the UI.
in your proxy code, find this part:
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`
},
body: JSON.stringify(req.body)
});
and change it to this:
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
body: JSON.stringify(req.body)
});
So:
- UI fetch can stay normal
- proxy fetch to OpenClaw is where the model-pin header must go
Thatās the part that actually talks to OpenClaw.
/home/p-gamer123/raspberry-chat/proxy.js:19
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
^^^^^^^^^^^^^^^^^^
SyntaxError: Unexpected string
at wrapSafe (node:internal/modules/cjs/loader:1637:18)
at Module._compile (node:internal/modules/cjs/loader:1679:20)
at Object..js (node:internal/modules/cjs/loader:1838:10)
at Module.load (node:internal/modules/cjs/loader:1441:32)
at Function._load (node:internal/modules/cjs/loader:1263:12)
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49
Node.js v22.22.1
p-gamer123@raspberrypi:~/raspberry-chat $
yep, that means thereās almost certainly a missing comma right above it.
use this exact headers block:
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
The important bit is the comma after the Authorization line. Without that, JavaScript sees the next string and throws Unexpected string.
done but still hanging
okay, then letās test the pinned model directly, without the UI and without the proxy confusion.
run this exact command:
curl -i http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
If that hangs too, the issue is the model/provider path itself, not your UI.
If it returns normally, then the issue is still in the proxy/UI layer.
Paste what happens.
/completions
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-H "x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 401 Unauthorized
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Type: application/json; charset=utf-8
Date: Mon, 20 Apr 2026 11:37:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 58
{"error":{"message":"Unauthorized","type":"unauthorized"}}curl: (6) Could not resolve host: openrouter
p-gamer123@raspberrypi:~/raspberry-chat $
got it, two things went wrong there:
- your
x-openclaw-modelheader was quoted wrong 401 Unauthorizedmeans$TOKENis probably blank/stale in that shell
use this exact command instead, in a shell where $TOKEN is actually set:
curl -i http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "x-openclaw-model: openrouter/meta-llama/llama-3.3-70b-instruct:free" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
If youāre not sure $TOKEN is set in this shell, say that and Iāll give you the fresh-token version again.
same error
yep, then letās stop relying on whatever $TOKEN used to be and make a fresh one in this same shell.
run this exact block:
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
curl -i http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "x-openclaw-model: openrouter/meta-llama/llama-3.3-70b-instruct:free" \
-d '{"model":"openclaw/default","messages":[{"role":"user","content":"hi"}]}'
that removes the token mismatch entirely, because the gateway and the curl will both use the same fresh token from the same terminal.
same
I need the exact output now, not just āsameā.
paste the full result from the terminal after running it, even if it looks ugly.
Right now I canāt tell whether āsameā means:
401 UnauthorizedCould not resolve host- syntax error
- or something else
paste the exact text and Iāll tell you the exact fix.
can u jst tekll me how to change the model. the ui freezing like this means that theres a typo/syntax error nin the cde
yep, keep it simple.
to change the model, only edit this one part in proxy.js:
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
That goes inside the server-side fetch("http://127.0.0.1:18789/v1/chat/completions", { ... }) block in your proxy.
So the full fetch should look like this:
const upstream = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
body: JSON.stringify(req.body)
});
That is the model-change part. Nothing else is required for the switch itself.
still not working maybe u errored in the ui
still not working i think u may have made an error in the ui
@sudden vale
@sudden vale
its still not working i think u might have errored in teh ui
yeah, I think the safest move is to reset the UI part to the known-good version and stop touching the model there.
replace your current request/response block with this exact chunk:
try {
const res = await fetch("/api", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "openclaw",
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change your name." },
{ role: "user", content: text }
]
})
});
removeTyping();
if (!res.ok) {
addBubble("ai", "Gateway error: " + res.status);
sendBtn.disabled = false;
return;
}
const data = await res.json();
const reply =
data.choices?.[0]?.message?.content ||
data.reply ||
data.response ||
data.message ||
JSON.stringify(data);
addBubble("ai", reply);
} catch (err) {
removeTyping();
addBubble("ai", "Failed to reach gateway.");
}
That is the UI-side block only.
Important:
- no
x-openclaw-modelin the UI - model pinning belongs only in the proxy fetch to OpenClaw
This should get the UI back to a clean working state.
tell me the code in one msg
try {
const res = await fetch("/api", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "openclaw",
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change your name." },
{ role: "user", content: text }
]
})
});
removeTyping();
if (!res.ok) {
addBubble("ai", "Gateway error: " + res.status);
sendBtn.disabled = false;
return;
}
const data = await res.json();
const reply =
data.choices?.[0]?.message?.content ||
data.reply ||
data.response ||
data.message ||
JSON.stringify(data);
addBubble("ai", reply);
} catch (err) {
removeTyping();
addBubble("ai", "Failed to reach gateway.");
}
try { const res = await fetch("/api", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "openclaw", messages: [ { role: "system", content: "Your name is Raspberry. Do not change your name." }, { role: "user", content: text } ] }) }); removeTyping(); if (!res.ok) { addBubble("ai", "Gateway error: " + res.status); sendBtn.disabled = false; return; } const data = await res.json(); const reply = data.choices?.[0]?.message?.content || data.reply || data.response || data.message || JSON.stringify(data); addBubble("ai", reply); } catch (err) { removeTyping(); addBubble("ai", "Failed to reach gateway."); }
yep, that UI block looks fine now.
so the UI side is back to normal.
the only place left for the model pin is the proxy fetch to OpenClaw. If you want, paste that proxy fetch("http://127.0.0.1:18789/v1/chat/completions", ...) block next and Iāll check the model header there.
try {
const res = await fetch("/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
model: "openclaw",
body: JSON.stringify({
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change y>
{ role: "user", content: text }
]
})
this good? will the model be changed now
no, that one is not good yet.
two problems:
model: "openclaw"is in the wrong place- this is still the UI fetch, so it still will not change the backend model by itself
the correct UI block is:
try {
const res = await fetch("/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "openclaw",
messages: [
{ role: "system", content: "Your name is Raspberry. Do not change your name." },
{ role: "user", content: text }
]
})
});
That fixes the UI syntax, but no, that alone will not pin the model.
The actual model change still has to happen in the proxy fetch to OpenClaw, not in this UI block.
it gives me 401 now
yep, 401 means the proxy token and gateway token donāt match again.
most likely you restarted node proxy.js in a shell where the token env wasnāt set.
run this in the same terminal where you start the proxy:
Ctrl+C
TOKEN="$(openssl rand -hex 32)"
openclaw config set gateway.auth.token "$TOKEN"
openclaw gateway restart
export OPENCLAW_GATEWAY_TOKEN="$TOKEN"
node proxy.js
that re-syncs the gateway + proxy token again.
401 is auth mismatch, not the model change itself.
now says ā ļø Agent couldn't generate a response. Please try again.
that message is too generic by itself, so the next step is to catch the real error in the gateway logs.
run this in a terminal:
openclaw logs --follow
then send one test message through your UI and paste the new lines that appear right after.
thatāll show whether itās:
- auth
- model/provider failure
- bad request format
- or something else.
09:16:16+00:00 info gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord client initialized as 1480508013600182384 (OpenClawAI); awaiting gateway readiness
09:16:16+00:00 info gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord gateway: Gateway websocket closed: 4014
09:16:16+00:00 error gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord: gateway closed with code 4014 (missing privileged gateway intents). Enable the required intents in the Discord Developer Portal or disable them in config.
09:23:45+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_ffe2c303-884e-4edd-929b-fbae581b5215 sessionId=60e77edc-23e7-4d93-b3cc-7b525d0c53da stopReason=stop payloads=0 ā surfacing error to user
09:23:45+00:00 info ā ļø Agent couldn't generate a response. Please try again.
09:23:54+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_b45e6f4e-cf9b-4b6a-b885-ac3f1d2c3b50 sessionId=b690cecb-d7cf-46b5-a139-cabe5d0b823f stopReason=stop payloads=0 ā surfacing error to user
09:23:55+00:00 info ā ļø Agent couldn't generate a response. Please try again.
Log tail truncated (increase --max-bytes).
09:25:34+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_fd0e840a-196e-4233-a17d-d7e0a9cbed98 sessionId=c983b28c-4186-4bb7-b1ec-f086254499ca stopReason=stop payloads=0 ā surfacing error to user
09:25:35+00:00 info ā ļø Agent couldn't generate a response. Please try again.
good, the Discord 4014 bit is unrelated to your Cloudflared UI. you can ignore that for this problem.
the real clue is this:
incomplete turn detected ... payloads=0
which means the model path stopped without returning usable output.
the tail you pasted is truncated, so we need a bigger slice.
run this:
openclaw logs --follow --max-bytes 200000
then send one test message through the UI and paste the lines from just before the incomplete turn detected warning.
that should show the actual provider/model error right before it dies.
=09:16:14+00:00 warn gateway/channels/discord {"subsystem":"gateway/channels/discord"} [default] Discord Message Content Intent is disabled; bot may not respond to channel messages. Enable it in Discord Dev Portal (Bot ā Privileged Gateway Intents) or require mentions.
09:16:14+00:00 info gateway/channels/discord {"subsystem":"gateway/channels/discord"} [default] starting provider (@OpenClawAI)
09:16:16+00:00 info gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord client initialized as 1480508013600182384 (OpenClawAI); awaiting gateway readiness
09:16:16+00:00 info gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord gateway: Gateway websocket closed: 4014
09:16:16+00:00 error gateway/channels/discord {"subsystem":"gateway/channels/discord"} discord: gateway closed with code 4014 (missing privileged gateway intents). Enable the required intents in the Discord Developer Portal or disable them in config.
09:23:45+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_ffe2c303-884e-4edd-929b-fbae581b5215 sessionId=60e77edc-23e7-4d93-b3cc-7b525d0c53da stopReason=stop payloads=0 ā surfacing error to user
09:23:45+00:00 info ā ļø Agent couldn't generate a response. Please try again.
09:23:54+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_b45e6f4e-cf9b-4b6a-b885-ac3f1d2c3b50 sessionId=b690cecb-d7cf-46b5-a139-cabe5d0b823f stopReason=stop payloads=0 ā surfacing error to user
09:23:55+00:00 info ā ļø Agent couldn't generate a response. Please try again.
09:25:34+00:00 warn agent/embedded {"subsystem":"agent/embedded"} incomplete turn detected: runId=chatcmpl_fd0e840a-196e-4233-a17d-d7e0a9cbed98 sessionId=c983b28c-4186-4bb7-b1ec-f086254499ca stopReason=stop payloads=0 ā surfacing error to user
09:25:35+00:00 info ā ļø Agent couldn't generate a response. Please try again.
those logs still arenāt giving the providerās internal error, theyāre only telling us:
the run stopped and produced 0 usable output
so at this point, the practical read is:
- the Discord 4014 stuff is unrelated
- your forced Llama/free route is very likely the thing failing silently
- OpenClaw is surfacing that as ācouldnāt generate a responseā
If you need it working now, my recommendation is:
- remove the forced model pin from the proxy
- go back to the last known-working model/default
- restart the proxy + gateway
If you want, I can give you the exact one-block code to remove the x-openclaw-model line cleanly and get you back to a working state first.
now how do i change model
youāve got two ways to change it:
1) change the default model for OpenClaw
openclaw models set <provider/model>
openclaw gateway restart
That changes the default model OpenClaw should use.
2) force your custom UI/proxy to use a specific model
in the proxyās server-side fetch to OpenClaw, add:
"x-openclaw-model": "<provider/model>"
inside the headers block.
short version:
- global default ā
openclaw models set ... - your custom UI specifically ā
x-openclaw-modelin the proxy
If you want, tell me the exact model you want now, and Iāll send the exact line/command only.
i want model: "meta-llama/llama-3.3-70b-instruct:free"
for your setup, use the OpenRouter-prefixed form:
openrouter/meta-llama/llama-3.3-70b-instruct:free
global default
openclaw models set openrouter/meta-llama/llama-3.3-70b-instruct:free
openclaw gateway restart
proxy header
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
important bit: in your setup itās not just meta-llama/..., it needs the openrouter/ prefix.
u forgot the comma btw do i do it under the authorisation stuffy
yep, good catch, and yes, put it under the Authorization line inside the same headers object.
use this exact block:
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"x-openclaw-model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
},
So:
- same
headers - same place as
Authorization - with a comma after the
Authorizationline
then it should work?
it should force your proxy requests to try that model, yes.
So if:
- the token is correct
- the proxy is restarted
- that model route is actually healthy
then yes, it should work.
small caveat: if that specific free route is flaky again, it can still fail even though the header is correct. So the header fixes the routing, not the modelās own reliability.
nope still agent couldnt generate a response. i think the codes wrong. or do u think i havent set up openrouter properly?
I donāt think your general OpenRouter setup is the problem, because earlier your auth/status output showed an OpenRouter API key was present.
so the more likely situation is:
- your code path is basically okay
- but that specific free Llama route is the part failing
easy way to prove that:
change just this line to:
"x-openclaw-model": "openrouter/auto"
If openrouter/auto works, then:
- your proxy code is fine
- your OpenRouter auth is fine
- that specific
meta-llama/...:freeroute is the thing breaking
If you want the fastest test, thatās the one Iād do next.
/auto gives gateway error:400
ah, thatās actually a useful clue.
In your status output, the default showed up as:
openrouter/openrouter/auto
so try the fully resolved form, not the short one.
use:
"x-openclaw-model": "openrouter/openrouter/auto"
instead of:
"x-openclaw-model": "openrouter/auto"
That double openrouter/... looks weird, but your status output suggests thatās how your build is resolving it.
still 400
okay, at this point I need the actual 400 body, not just the status code.
run this and paste the full output:
curl -i http://127.0.0.1:8081/api \
-H "Content-Type: application/json" \
-d '{"model":"openclaw","messages":[{"role":"user","content":"hi"}]}'
the reason is simple: 400 could mean:
- bad model override format
- unsupported header in your build
- malformed request body
- provider rejection
the body text will tell us which one.
openrouter/auto is not allowed for agent main
perfect, that explains the 400.
it means:
- your code/header format is okay enough to be parsed
- but
openrouter/autois not in the allowed model list foragent main
So:
openrouter/autoā rejected by allowlistopenrouter/meta-llama/llama-3.3-70b-instruct:freeā allowed, but that route seems to be the one failing to generate output
So this is not just a typo now.
If you want openrouter/auto, you need to add it to the configured/allowed models in your model config.
If you want to keep using the Llama free route, then the header is the right idea, but the problem is likely that that specific route is flaky / returning nothing.
so how do i fix it?
youāve basically got two fixes now:
easiest fix, recommended
stop forcing the flaky free route.
- remove the
x-openclaw-modelline from the proxy - use your normal default model again
if you specifically want openrouter/auto
you need to add it to the agentās allowed models.
do:
openclaw configure --section model
then:
- add
openrouter/autoto the configured/allowed models - save
- restart the gateway
after that, you can either:
- leave the proxy with no model pin and let the default handle it
- or pin to:
"x-openclaw-model": "openrouter/auto"
important reality check
your meta-llama/...:free route looks allowed but unstable, so there may be nothing to āfixā there besides choosing a more reliable route.
If you want, I can give you the exact one-block code to remove the model pin and get you back to working first.
i want a free meta llama model running
got it, but the important bit is:
you donāt just want āa Meta Llama modelā, you want a free Meta Llama route that actually works.
right now, the one you pinned looks like itās allowed but not reliably returning output in your setup.
So the practical fix is:
- run
openclaw configure --section model - pick a different free Meta Llama route if one is available there
- save
- restart the gateway
If you want stable free Meta Llama, local/self-hosted is usually more predictable than random free hosted routes.
For current route availability, Iād also check #models, because free model availability changes a lot:
how do ichange openrouter/auto to a diff model
if you want to replace openrouter/auto as the default, do:
openclaw models set <provider/model>
openclaw gateway restart
for example, with the one you were trying earlier:
openclaw models set openrouter/meta-llama/llama-3.3-70b-instruct:free
openclaw gateway restart
and if your proxy code still has:
"x-openclaw-model": "..."
remember that header will override the default, so either:
- change that header too
- or remove it if you want the default model to take over.
i just want a free llama model. ok?
yep, totally fair.
then keep it simple:
- run
openclaw configure --section model - pick a free Llama route
- save
- restart
openclaw gateway restart
and if your proxy still has an x-openclaw-model header, either:
- remove it, so the new default is used
- or set it to the exact free Llama route you picked
Thatās the clean path. No auto, no weird override guessing.
what model will openrouter/auto give me
there isnāt one fixed answer.
openrouter/auto can route you to different backend models dynamically, so itās not a stable āthis always equals Xā setting.
If you want a guaranteed specific model, donāt use auto, pick a specific provider/model instead.
For current real-world behavior, best bet is:
- send a test request
- check your provider usage/logs
- ask in #models for fresh community experience
Docs:
k its giving me agent failed to generate a response what doi do
Copilot is almost certainly hallucinating here ā there is no separate āgateway binaryā you need from the creator. The Gateway ships with openclaw.
If youāre now seeing āagent failed/couldnāt generate a responseā, the tunnel is probably no longer the main issue; that usually means the Gateway received the message but the agent/model run failed.
Run these on the machine running OpenClaw:
openclaw --version
openclaw status --all
openclaw models status
openclaw infer model run --prompt "Reply exactly: smoke-ok" --json
openclaw logs --follow
Also, for the Cloudflare part:
curl -i http://127.0.0.1:18789/healthz
curl -i https://YOUR-CLOUDFLARE-DOMAIN/healthz
Expected: JSON like:
{"ok":true,"status":"live"}
If / returns HTML, thatās normal ā the Gateway serves the Control UI there. If /healthz through Cloudflare gives a Cloudflare 404, your tunnel ingress is pointing at the wrong place; it should route to the local Gateway, usually http://127.0.0.1:18789.
If your browser is using a Cloudflare domain, you may also need the exact origin allowed:
openclaw config set gateway.controlUi.allowedOrigins '["https://YOUR-CLOUDFLARE-DOMAIN"]' --strict-json
openclaw gateway restart
Security note: the dashboard is an admin surface, so donāt expose it publicly without Cloudflare Access / strong auth.
Docs:
https://docs.openclaw.ai/help/faq
https://docs.openclaw.ai/gateway/troubleshooting
https://docs.openclaw.ai/web/dashboard
so how do i fix it? auth issues give the code 401 or 400 but this isnt that so it must be model issues
Youāre mixing two different layers:
- Cloudflare 404 = tunnel/ingress/path problem. The request is probably not reaching OpenClaw.
- āAgent failed to generate a responseā = OpenClaw reached the agent, but the model/runtime failed.
Fix in this order:
openclaw --version
openclaw gateway status
curl -i http://127.0.0.1:18789/healthz
/ returning HTML is normal. Test /healthz; it should return:
{"ok":true,"status":"live"}
Then test Cloudflare:
curl -i https://YOUR-DOMAIN/healthz
If local /healthz works but Cloudflare /healthz is 404, fix your cloudflared ingress. It should look roughly like:
ingress:
- hostname: YOUR-DOMAIN
service: http://127.0.0.1:18789
- service: http_status:404
Then restart cloudflared.
For the browser/dashboard origin, also set:
openclaw config set gateway.controlUi.allowedOrigins '["https://YOUR-DOMAIN"]' --strict-json
openclaw gateway restart
After that, test the model separately:
openclaw models status
openclaw infer model run --prompt "Reply exactly: smoke-ok" --json
openclaw logs --follow
If the smoke test fails, thatās the model/auth issue. Paste the output of:
openclaw --version
openclaw status --all
openclaw models status
Docs:
https://docs.openclaw.ai/gateway/troubleshooting
https://docs.openclaw.ai/web/dashboard
works
Nice, glad itās working š
If that fixed it, please right-click the solution message ā Apps ā ā Mark Solution so Answer Overflow indexes it for future folks. š¦
but can u tell me whats the issue with the model?
Yep ā the āmodel issueā was basically:
- OpenClaw reached the agent.
- The selected OpenRouter/free Llama route ended with 0 usable output.
- Logs showed
incomplete turn detected ... payloads=0, so OpenClaw surfaced: āAgent couldnāt generate a response.ā
So it wasnāt Cloudflared at that point, and probably not your gateway token. It was the model route/pin being unreliable or not returning a valid response.
Also earlier: openrouter/auto is not allowed for agent main means your model allowlist rejected openrouter/auto. Thatās a config/allowlist issue, separate from the free Llama route failing.
Fast proof:
openclaw models status
openclaw infer model run --prompt "Reply exactly: smoke-ok" --json
If another model works but that free route doesnāt, the route is the problem. For fresh route recommendations, ask in #models since free OpenRouter routes change a lot.
is it safe to turn off my raspberrypi if its realllllly hot and its just displaying a grey screen
Yes ā if itās really hot and stuck on a grey screen, turn it off.
Best order:
- If you can SSH in, run:
sudo shutdown now - If itās frozen/no SSH, unplug power. Thereās a small SD-card corruption risk, but overheating is the bigger concern.
- Let it cool completely before powering back on.
- Check airflow, case, heatsink/fan, and power supply before retrying.
Donāt keep it running hot just to preserve OpenClaw. Once itās cooled down, we can debug the Cloudflared/OpenClaw part safely.
its not doing anything on screen
Then donāt wait on the screen ā power it off now.
If itās unresponsive and very hot:
- Unplug the Raspberry Pi power cable.
- Leave it off for 10ā15 minutes to cool.
- Donāt touch the board/heatsink if itās too hot.
- Before powering back on, make sure it has airflow / fan / heatsink and isnāt enclosed somewhere hot.
Thereās some SD-card risk from a hard power cut, but a frozen overheating Pi is the bigger problem.
the fan keeps changing speeds so its running smth what do i do
The fan changing speed just means the Pi/fan controller still has power ā it doesnāt mean itās healthy.
Do this:
- Try SSH once:
ssh pi@<your-pi-ip> sudo shutdown now - If SSH doesnāt connect quickly, unplug the power.
- Leave it off until itās fully cool.
Donāt keep waiting because āsomething is runningā ā if itās very hot + grey screen + no response, safe shutdown probably isnāt happening anyway. Hard power-off is the right call here.
gateway error 500
If the Pi was overheating, first make sure itās cooled down before debugging.
A 500 gateway error means something server-side is failing now ā either OpenClaw crashed/errored, or Cloudflare is reaching the wrong thing. Donāt guess from the browser page; check local first:
openclaw --version
openclaw gateway status
curl -i http://127.0.0.1:18789/healthz
openclaw status --all
Then check logs:
openclaw logs --follow
Rule of thumb:
- local
/healthzfails ā OpenClaw/gateway issue - local
/healthzworks but Cloudflare URL gives 500 ā Cloudflared tunnel/ingress issue - chat/API request gives 500 but
/healthzworks ā likely model/runtime error; logs will say why
Paste openclaw --version, openclaw gateway status, and the /healthz curl output.
i feel like its the gateway
It could be the gateway, yeah ā but we can split it cleanly.
Run these on the Raspberry Pi:
openclaw --version
openclaw gateway status
curl -i http://127.0.0.1:18789/healthz
openclaw logs --follow
If /healthz does not return something like:
{"ok":true,"status":"live"}
then yes, itās a local OpenClaw gateway problem.
If local /healthz works but your Cloudflare URL fails, then the gateway is fine and Cloudflared is pointing at the wrong host/port/path.
Also: there is no separate magic āgateway binaryā you need from the creator ā itās part of OpenClaw.
the ui works the proxy works (I can tell cuz the proxy feeds a message from raspberry to the ui thats not from the gateway) and it says http/1.1 200 ok
That means the web/UI layer is alive, but it does not prove the OpenClaw gateway connection is alive.
Important distinction:
HTTP/1.1 200 OKfrom/usually just means āthe UI HTML loadedā- the actual gateway health check is:
curl -i http://127.0.0.1:18789/healthz - and through Cloudflare:
curl -i https://YOUR-DOMAIN/healthz
If your proxy is feeding a Raspberry Pi message to the UI not from the gateway, then your UI/proxy may be working while the UIās backend/WebSocket/API route to OpenClaw is broken.
Check the browser dev console/network tab for failed requests, especially WebSocket/connect calls. Also run:
openclaw gateway status
openclaw status --all
openclaw logs --follow
The useful test is: does /healthz return JSON locally and through the domain? If yes, gateway is reachable; then the remaining issue is probably auth/model/runtime, not Cloudflared.
no json from /healthz
Then yes ā thatās the key problem.
If /healthz does not return JSON, either:
- OpenClaw gateway is not running, or
- youāre curling the wrong port/service, or
- your proxy is serving the UI fallback HTML instead of forwarding
/healthzto OpenClaw.
Run this on the Pi:
openclaw --version
openclaw gateway status
openclaw status --all
curl -i http://127.0.0.1:18789/healthz
If openclaw gateway status shows a different probe URL/port, curl that exact URL instead.
Also check what owns the port:
ss -ltnp | grep 18789
If / gives HTML but /healthz doesnāt give JSON, your browser UI can load, but the actual gateway route is not being reached. Donāt debug the model yet ā fix /healthz first.
k how do i fix
Do this in order, on the Pi:
openclaw gateway restart
openclaw gateway status
curl -i http://127.0.0.1:18789/healthz
If /healthz still isnāt JSON:
ss -ltnp | grep 18789
openclaw logs --follow
What to fix based on result:
- Nothing listening on 18789 ā gateway isnāt running:
openclaw gateway start - Something else is on 18789 ā your proxy/UI is occupying the OpenClaw port. Stop that or move it; OpenClaw gateway needs 18789.
- Gateway runs on a different port ā use the URL shown by
openclaw gateway status. - Local
/healthzworks, Cloudflare/healthzdoesnāt ā fix cloudflared ingress to point directly at OpenClaw:ingress: - hostname: YOUR-DOMAIN service: http://127.0.0.1:18789 - service: http_status:404
For now, bypass your custom proxy entirely. First make http://127.0.0.1:18789/healthz return JSON locally. Then put Cloudflared in front of it.
cannot get heathz when i run it thru cloudflared but grep says that openclaw is running on 18789
That points to Cloudflared ingress, not OpenClaw.
First: make sure the path is spelled exactly /healthz ā not /heathz.
Run:
curl -i http://127.0.0.1:18789/healthz
curl -i https://YOUR-DOMAIN/healthz
cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://YOUR-DOMAIN/healthz
Cloudflareās own docs say ingress rules are matched top-to-bottom, and cloudflared tunnel ingress rule <url> shows which rule matched. If it matches your catch-all http_status:404, your hostname/path rule is wrong. (developers.cloudflare.com)
Your tunnel config should be basically:
ingress:
- hostname: YOUR-DOMAIN
service: http://127.0.0.1:18789
- service: http_status:404
Then restart cloudflared. If local /healthz works but domain /healthz doesnāt, donāt touch models/gateway yet ā the tunnel is not forwarding that request to OpenClaw.
but cloudflared is working its the ui forwarding to the gate thats not
Right ā then the broken part is your UI/proxy routing to OpenClaw, not Cloudflared itself.
Most common mistake: the browser/UI tries to call:
http://127.0.0.1:18789
from the userās browser. That points to the userās computer, not the Raspberry Pi.
Fix options:
Best/simple: point Cloudflared directly at OpenClaw and skip the custom UI proxy:
ingress:
- hostname: YOUR-DOMAIN
service: http://127.0.0.1:18789
- service: http_status:404
If keeping your own UI/proxy: the proxy must forward requests server-side to:
http://127.0.0.1:18789
and it must support WebSocket upgrade, not just normal HTTP.
Test from the Pi:
curl -i http://127.0.0.1:18789/healthz
Then test through your UI/proxy route:
curl -i https://YOUR-DOMAIN/healthz
If first works and second doesnāt, your UI/proxy is not forwarding /healthz/gateway routes to OpenClaw correctly. Docs: https://docs.openclaw.ai/web/dashboard
but i havent changed anything... do u think the files got corrupted?
Possible, but I wouldnāt assume corruption first.
If the Pi overheated/froze, SD-card corruption is possible. But if OpenClaw is still running on 18789, this is more likely routing/proxy/service state.
Run these:
openclaw --version
curl -i http://127.0.0.1:18789/healthz
openclaw gateway status
openclaw logs --follow
Then check for SD/filesystem problems:
dmesg -T | grep -Ei "i/o error|ext4|mmc|corrupt|read-only"
df -h
Interpretation:
- local
/healthzreturns JSON ā OpenClaw files are probably fine; fix proxy/cloudflared route. - local
/healthzfails + logs show OpenClaw errors ā gateway/config issue. dmesgshowsI/O error,mmc,EXT4-fs error, or filesystem wentread-onlyā yeah, likely SD-card/filesystem damage. Shut down, back up what you can, reflash/restore.
Donāt run repair commands blindly while the system is mounted. First paste the outputs above.
but why would anything else be wrong cuz i only shut it down without changing any files
Because a shutdown can change state even if you didnāt edit files.
A few realistic causes:
- If you unplugged it while services were writing logs/cache/config, the SD card could have a small bad write.
- Services can restart in a different order after boot ā OpenClaw, your UI, and cloudflared may not all be ready at the same time.
- The custom UI/proxy may have started, but OpenClaw may not have started correctly.
- The IP/port target may still exist, but the route being forwarded is different.
- A hot Pi can throttle/freeze and leave services half-started.
That said, donāt panic ā this is probably not āeverything corrupted.ā
The truth test is still:
curl -i http://127.0.0.1:18789/healthz
If that works locally, OpenClaw is fine and only the forwarding/proxy path is wrong.
If it doesnāt, run:
openclaw gateway restart
openclaw gateway status
openclaw logs --follow
Then weāll know instead of guessing.
healthz says json like ok true status live
Nice ā that means OpenClaw gateway is healthy. Not corrupted, not missing a gateway binary. š¦
So the broken bit is between Cloudflared/custom UI/proxy and OpenClaw.
Next test both paths:
curl -i http://127.0.0.1:18789/healthz
curl -i https://YOUR-DOMAIN/healthz
If local is JSON but domain is not, fix Cloudflared/proxy routing.
Recommended simple config:
ingress:
- hostname: YOUR-DOMAIN
service: http://127.0.0.1:18789
- service: http_status:404
If youāre using a custom UI proxy instead of sending Cloudflared directly to OpenClaw, it must forward /healthz and the gateway WebSocket/API routes to http://127.0.0.1:18789. If it only serves the UI HTML, youāll keep getting HTML/404/500 instead of gateway JSON.
yaml isnt a valid cmd
Yep ā sorry, that block was not a command. Itās the contents that go inside your cloudflared config file.
Usually the file is one of these:
~/.cloudflared/config.yml
/etc/cloudflared/config.yml
Open it with an editor, for example:
nano ~/.cloudflared/config.yml
Then make the ingress: section look like:
ingress:
- hostname: YOUR-DOMAIN
service: http://127.0.0.1:18789
- service: http_status:404
Save, then restart cloudflared:
sudo systemctl restart cloudflared
Then test:
curl -i https://YOUR-DOMAIN/healthz
If you donāt know where the config is, run:
cloudflared tunnel ingress validate
but why do i need to edit it it asn changed
You might not need to edit it. First verify what Cloudflared is actually doing.
A config can be unchanged but still wrong because:
- cloudflared restarted and loaded a different config file
- the service started before/without the same env/working dir
- the hostname rule doesnāt match your current domain
- a catch-all rule is catching
/healthz - your custom UI/proxy is intercepting the path
Run these:
cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://YOUR-DOMAIN/healthz
curl -i http://127.0.0.1:18789/healthz
curl -i https://YOUR-DOMAIN/healthz
The important one is:
cloudflared tunnel ingress rule https://YOUR-DOMAIN/healthz
It tells you exactly which ingress rule Cloudflared will use. If it doesnāt point to http://127.0.0.1:18789, then thatās why the domain /healthz fails.
with the 1st one no config file was found
Aha ā that explains the ānothing changedā confusion.
cloudflared tunnel ingress validate saying no config file found means the command isnāt seeing a local config.yml in the default place. Your running cloudflared may be using:
- a different config path
- a systemd service with
--config /some/path - a dashboard/quick tunnel setup with no local ingress config
- a different userās home directory
Find what the running service uses:
ps aux | grep '[c]loudflared'
systemctl status cloudflared --no-pager
systemctl cat cloudflared
Look for --config ... or tunnel run ....
If you find a config path, validate it explicitly:
cloudflared tunnel --config /PATH/TO/config.yml ingress validate
cloudflared tunnel --config /PATH/TO/config.yml ingress rule https://YOUR-DOMAIN/healthz
If thereās no config path at all, youāre probably not running a local config-based tunnel, so editing ~/.cloudflared/config.yml wonāt affect the running tunnel.
what operating system are you on ?
If you mean the Raspberry Pi, run:
cat /etc/os-release
uname -a
Thatāll tell us the OS/distro and kernel.
This threads is getting very long - please start a new one
Agreed ā this oneās gotten too tangled.
Please start a new #help thread and include:
Goal: expose OpenClaw through Cloudflared on Raspberry Pi
Confirmed:
- local http://127.0.0.1:18789/healthz returns {"ok":true,"status":"live"}
- Cloudflared/config path is unclear / validate said no config file found
Still broken:
- domain /healthz does not return the same JSON
Paste:
- openclaw --version
- cat /etc/os-release
- ps aux | grep '[c]loudflared'
- systemctl cat cloudflared
- curl -i http://127.0.0.1:18789/healthz
- curl -i https://YOUR-DOMAIN/healthz
Thatāll make it much easier to help without all the old noise.