OpenClaw proxy configuration: bugs and fixes I shipped

OpenClaw proxy configuration: bugs I hit across 6 GitHub issues plus the env vars, nginx block, and openclaw.json5 fixes that finally stuck.

13 min read

OpenClaw proxy configuration three-layer diagram with laptop, central proxy, and gateway loopback branch

TL;DR: OpenClaw proxy configuration is three independent layers. Outbound HTTP_PROXY and HTTPS_PROXY env vars (set them, then add NO_PROXY=localhost,127.0.0.1 or browser control breaks via issue #31219), an inbound nginx reverse proxy with WebSocket upgrade headers and proxy_read_timeout 86400 in front of gateway port 18789, and an optional LLM-routing proxy like LiteLLM for cost control.

OpenClaw proxy configuration breaks in three places at once, and most reader pain comes from confusing the three layers. The env-var layer is partially broken in 2026.4.x.

web_fetch (#61480), the Chrome CDP probe (#31219), and the Telegram channel via grammy/undici (#28524) each ignore HTTP_PROXY independently. The undici monkey-patch from #2102 covers the main process.

The inbound layer is nginx or Caddy on 443, upgrading the WebSocket on /, and forwarding to 127.0.0.1:18789 with proxy_read_timeout 86400. Skip trusted-proxy auth mode in multi-node setups until issue #43561 ships a fix.

The LLM-routing layer is LiteLLM on port 4000 or RelayPlane on 4100 between OpenClaw and 100+ providers. Same word, different problem.

I hit the browser-CDP one first. Chrome launched, OpenClaw reported Failed to start Chrome CDP on port 18800, and the gateway logs were silent. Two days into debugging I learned Node was sending the 127.0.0.1 probe through the corporate proxy. One NO_PROXY line fixed it.

Set HTTP_PROXY, HTTPS_PROXY, and NO_PROXY (the env-var path)

The recommended way to set outbound proxy is three env vars, set in the same shell or service file that starts the gateway. Add NO_PROXY=localhost,127.0.0.1,::1,.internal first, or the Chrome CDP probe routes through the proxy and crashes (issue #31219). Run it after every change.

On Linux and macOS:

export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
export ALL_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1,::1,.internal

For a systemd-managed gateway, the env vars belong in the unit file. Editing ~/.bashrc won't help. Drop these into /etc/systemd/system/openclaw.service:

[Service]
Environment=HTTP_PROXY=http://proxy.example.com:8080
Environment=HTTPS_PROXY=http://proxy.example.com:8080
Environment=NO_PROXY=localhost,127.0.0.1,::1,.internal

Then systemctl daemon-reload && systemctl restart openclaw. Baseline runtime is Node.js 22 LTS (22.12.0+ for production); older Node fails in ways that look like proxy bugs but aren't.

Windows is the painful one. PowerShell $env:HTTP_PROXY=... only sticks for the current session. Use setx HTTP_PROXY http://... for persistence, then open a new terminal. OpenClaw 2026.2.24 also broke HTTP_PROXY=http://127.0.0.1:10808 on Windows 10 (issue #26455); the Windows 2026.2.24 regression is its own surface.

SOCKS5 works for the main process via the same undici dispatcher. Set ALL_PROXY=socks5://user:pass@host:port. Browser CDP and Telegram still ignore it.

Verify with openclaw doctor and a curl --proxy $HTTP_PROXY https://api.anthropic.com sanity check. If curl passes but doctor stays red, the bug is in OpenClaw, not your network.

The per-component proxy gaps in 2026.x (web_fetch, browser, Telegram)

One env-var pass is not enough. Three tools each ignore HTTP_PROXY independently in 2026.x; the shared root cause is Node 18+'s undici client not auto-detecting proxy env vars the way curl does. Lessons learned the hard way.

Component / surfaceVersionIssueCurrent behavior
Main process2026.4.x#2102Honors HTTP_PROXY after undici monkey-patch only
web_fetch tool2026.4.2#61480dispatcherPolicy defaults to direct
Browser CDP probe2026.3.1#31219Routes 127.0.0.1:18800 through proxy, crashes
Telegram (grammy/undici)2026.2.26#28524setMyCommands fails, channel never connects
Windows main process2026.2.24#26455Regression vs prior version, closed not-planned
Per-component proxy gaps observed in OpenClaw 2026.2.x to 2026.4.x

Most recent is the 2026.4.2 web_fetch regression. The dispatcher resolves through createPinnedDispatcher(...) with params.dispatcherPolicy set to direct, regardless of env vars. Nothing in the logs flags the bypass; I caught it only because outbound calls were failing for one tool and working everywhere.

The browser-CDP failure in 2026.3.1 has a one-line fix. Chrome listens on 127.0.0.1:18800; OpenClaw's browser relay calls isChromeReachable(), which probes http://127.0.0.1:18800/json/version.

Without NO_PROXY=localhost,127.0.0.1, Node sends that probe through the corporate proxy, gets rejected, and OpenClaw reports Failed to start Chrome CDP on port 18800 for profile "openclaw". Chrome is fine. The probe is broken.

OpenClaw Chrome CDP proxy error in terminal with NO_PROXY localhost fix annotation

Third surface is the Telegram channel via grammy, which wraps undici. The channel call fails with gateway/channels/telegram Telegram command sync failed: HttpError: Network request for 'setMyCommands' failed!. If you're running the Telegram channel behind a proxy, this is the bug.

The undici dispatcher problem in one paragraph

Node 18+ replaced http.Agent with undici as the default HTTP client. undici does not read HTTP_PROXY/HTTPS_PROXY from the environment; curl does. That mismatch is why "set HTTP_PROXY" worked in the Node 16 era and stopped when OpenClaw moved to Node 18+. The shared fix is a global dispatcher override at process start.

The community monkey-patch from issue #2102

The fix operators run in the wild lives in issue #2102. Find openclaw.mjs, inject the patch right after the imports, restart the gateway:

import { ProxyAgent, setGlobalDispatcher } from "undici";
const proxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxy) {
  setGlobalDispatcher(new ProxyAgent(proxy));
}

readlink -f "$(which openclaw)" resolves the symlink to the actual mjs file. The patch covers the main process and the web_fetch dispatcher in older versions. Re-apply after every upgrade until issue #60035 ships a config-file proxy option.

Persist config in ~/.config/openclaw/openclaw.json5 (per-model and per-provider)

For persistent per-model routing, the config file beats env vars. It lives at ~/.config/openclaw/openclaw.json5 on Linux and macOS, %APPDATA%\openclaw\openclaw.json5 on Windows. Useful for routing OpenAI through a corporate proxy while Ollama runs direct.

Resolution priority is the part nobody writes down clearly:

SourcePriorityExample
models[].proxy (per-model)Highestmodels[0].proxy: "http://proxy.a:8080"
Top-level proxy blockMediumproxy: { http: "http://proxy.b:8080" }
Env vars (HTTP_PROXY/HTTPS_PROXY)Lowexport HTTP_PROXY=http://proxy.c:8080
Direct (no proxy)Defaultnothing set
Proxy resolution priority in OpenClaw

The top-level block looks like this in practice:

{
  proxy: {
    http: "http://proxy.example.com:8080",
    https: "http://proxy.example.com:8080",
    socks5: "socks5://user:pass@host:1080"
  },
  models: [
    {
      name: "anthropic/claude-sonnet-4-5",
      proxy: "http://corp-proxy.internal:3128"
    },
    {
      name: "ollama/llama3.3",
      proxy: null
    }
  ]
}

The proxy: null on Ollama is the part most operators skip. Without it the top-level proxy applies, the Ollama call returns a 502, and the agent falls back to the next model. For broader per-model routing, the same models[] block is where everything else lives.

I learned the priority chain the embarrassing way. Set ALL_PROXY in my shell, expected it to override everything, and watched it lose to a models[0].proxy entry I'd written weeks earlier and forgotten about. Half a morning of tcpdump before I read the resolution rule above.

Proxy auth can embed credentials in the URL (http://user:pass@host:port) or use a separate proxy.auth block. URL-embedded creds work everywhere; the structured block is easier to scrub from logs.

Self-hosting proxy config costs three env vars, a json5 file, and three GitHub issues. OpenclawVPS ships with the env, NO_PROXY list, and per-model config pre-baked. Provisioning takes 47 seconds. Plans start at $19/month.

Put nginx in front of the gateway (reverse proxy with WebSocket)

Bind the gateway to loopback first. The canonical inbound setup terminates TLS on nginx, upgrades the WebSocket on /, and forwards to loopback. Set gateway.bind: 127.0.0.1 on port 18789; binding the gateway to 127.0.0.1 is defense layer one.

OpenClaw nginx reverse proxy architecture with TLS termination WebSocket upgrade and loopback gateway lane

The first time I shipped this behind nginx, I left the defaults in place. Chrome chat sessions died mid-response after sixty seconds. Two evenings of journalctl thinking the gateway was crashing, before I found proxy_read_timeout 86400 in a dev.to comment. The WebSocket was getting killed by nginx's default 60s timeout.

Here's the minimum viable server block, with the WebSocket headers that actually matter:

server {
    listen 443 ssl http2;
    server_name openclaw.example.com;
 
    ssl_certificate     /etc/letsencrypt/live/openclaw.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/openclaw.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
 
    add_header Strict-Transport-Security "max-age=63072000" always;
 
    location / {
        proxy_pass http://127.0.0.1:18789;
        proxy_http_version 1.1;
 
        # WebSocket upgrade, without these chat breaks
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
 
        # 24 hours; chat sessions outlast nginx's default 60s
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
 
        # Forwarding headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Twenty-four hours matches the agent-running-overnight pattern. The gateway idles around 1 GB once warm, so loopback binding doesn't change hardware requirements for the box behind nginx.

WebSocket pairing codes have a one-hour TTL. If your proxy strips the Upgrade or Connection header (some default WAF rules do), pairing fails silently.

Caddy is the four-line equivalent and auto-handles WebSocket upgrades plus certs:

openclaw.example.com {
    reverse_proxy 127.0.0.1:18789
    encode gzip
}

Tradeoff: less visibility when something breaks.

Trusted-proxy auth mode: when to use it (and the #43561 bug)

gateway.auth.mode: trusted-proxy delegates gateway auth to an identity-aware reverse proxy that injects x-forwarded-user, x-forwarded-email, and x-forwarded-groups headers. Use case: Pomerium, Authelia, or oauth2-proxy in front of a multi-user OpenClaw deployment.

The gateway.trustedProxies list is the IP allow-list for proxies allowed to inject those headers. Headers from anything outside get stripped.

{
  gateway: {
    auth: {
      mode: "trusted-proxy"
    },
    trustedProxies: ["10.0.0.5", "10.0.0.6"]
  }
}

A 2026.3.12 bug worth knowing: issue #43561 documents that in trusted-proxy mode, the shared-secret fallback fires before nginx proxy headers are evaluated, so the auth chain rejects worker nodes before their proxy identity is even checked.

I learned this the embarrassing way. Half a weekend rotating shared secrets across a two-node Pomerium setup, convinced I'd misconfigured the worker token. The token was fine. One flip to token-auth and every worker came back green.

Stay in token-auth for multi-node setups until the upstream fix lands.

Single-node trusted-proxy mode behind nginx works fine in 2026.4.x. Multi-node behind an identity-aware proxy is the broken setup the "ultimate guides" omit when they pitch trusted-proxy as production-ready. That part is broken in 2026.4.x.

Layer an LLM-routing proxy on top (LiteLLM, RelayPlane, OpenRouter)

An LLM-routing proxy routes API calls between providers and models; network proxy (everything above) routes packets. Same word, different problem.

LiteLLM Proxy gives OpenClaw access to 100+ providers through one OpenAI-compatible endpoint, making it easy to change models mid-session. Default port 4000. RelayPlane is the Node 18+ self-hosted alternative on localhost:4100. OpenRouter is the hosted equivalent.

LiteLLM hookup in six steps:

  1. Install the LiteLLM proxy with pip install 'litellm[proxy]'.
  2. Write a config.yaml that lists your providers and model aliases.
  3. Start the proxy with litellm --config config.yaml --port 4000.
  4. Point OpenClaw's models[].endpoint at http://localhost:4000/v1 in openclaw.json5.
  5. Restart the gateway so the new endpoint loads.
  6. Verify with openclaw doctor that the model surface is green.

The OpenClaw side is one line in openclaw.json5:

{
  models: [
    {
      name: "litellm/gpt-4o",
      endpoint: "http://localhost:4000/v1"
    }
  ]
}

Where this saves money is the cost spread between lightweight and frontier models. A scheduling agent runs on a DeepSeek-tier model at $0.28/M tokens while the reasoning agent uses a $10-30/M one. LiteLLM routes per-request.

20-60×
Cost spread between lightweight ($0.50/M tokens) and frontier ($10-30/M tokens) LLMs in 2026 pricing.

When this layer isn't worth it: single-model setups or workloads small enough that routing overhead exceeds the spread. For a multi-agent team running 24/7, LiteLLM pays for itself in a week. Test it before you ship.

Verify and debug: openclaw doctor, NO_PROXY, and Docker/WSL gotchas

openclaw doctor walks env vars, json5 config, the gateway socket, and per-channel surfaces in one pass. Run it after every config change. The three-command sequence:

env | grep -i proxy
curl -v --proxy $HTTP_PROXY https://api.anthropic.com
journalctl -u openclaw -f | grep -i proxy

If env is clean and curl succeeds but the journal shows no proxy uptake, the bug is in OpenClaw's dispatcher (#2102). The fix is one line.

OpenClaw proxy debugging decision tree

Five-node tree: env vars set, then four checks (main process, web_fetch, browser CDP, Telegram).

env vars set?main process #2102web_fetch #61480browser CDP #31219Telegram #28524Any failed branch: that surface needs its own dispatcher fix.

Docker doesn't inherit host env vars. Pass them explicitly with -e HTTP_PROXY -e HTTPS_PROXY -e NO_PROXY, or list each in docker-compose's environment: block.

WSL2 is the other trap. Windows host proxy settings don't reach WSL2 by default. Add [wsl2] networkingMode=mirrored to .wslconfig then wsl --shutdown, or set HTTP_PROXY inside WSL. Issue #28524's Telegram bug was a WSL networking case at root.

Tailscale and Cloudflare Tunnel skip the public reverse-proxy story entirely, at the cost of locking to a vendor.

Three config rewrites in and openclaw doctor is still red. OpenclawVPS pre-bakes the env vars, NO_PROXY list, nginx block with WebSocket headers, and 24-hour proxy_read_timeout. Done in 47 seconds.

The proxy setup I run now

After three layers and six GitHub issues, my production setup is short. NO_PROXY catches localhost. The undici monkey-patch lives in openclaw.mjs. Nginx carries the 24-hour read timeout and three WebSocket headers. Trusted-proxy mode stays off. LiteLLM sits on 4000. That's the whole proxy story for an open-source AI agent.

Or skip the stack. OpenclawVPS provisions a VPS in Falkenstein, Nuremberg, or Helsinki with the env, NO_PROXY, nginx config, and 24-hour timeout pre-baked. BYO-API-key, EU regions, 47-second deploys.

Get started with OpenclawVPS →


Frequently asked questions

Does OpenClaw respect HTTP_PROXY and HTTPS_PROXY environment variables?
Partially. The main process picks them up via the undici monkey-patch (#2102). In 2026.4.x web_fetch (#61480), browser CDP (#31219), and Telegram (#28524) each ignore them. Set the env vars, add NO_PROXY=localhost,127.0.0.1, verify with openclaw doctor.
How do I run OpenClaw behind an nginx reverse proxy?
Bind the gateway to 127.0.0.1:18789, terminate TLS on nginx, forward / with WebSocket upgrade headers (Upgrade, Connection, proxy_http_version 1.1) and proxy_read_timeout 86400. Skip trusted-proxy mode in multi-node until #43561 is fixed.
Can OpenClaw use a SOCKS5 proxy?
Yes for the main process. export ALL_PROXY=socks5://user:pass@host:port works through the undici-patched dispatcher. SOCKS5 does NOT flow through browser CDP or Telegram. For Shadowsocks-style local proxies, terminate to a local HTTP proxy first.
What is gateway.trustedProxies in OpenClaw and when should I use it?
It's an IP allow-list naming the reverse proxies allowed to inject identity headers like x-forwarded-user. Use it only with an identity-aware proxy (Pomerium, Authelia) in front. Skip it for plain TLS termination; token auth is simpler. Issue #43561 breaks the mode in multi-node nginx setups.
How do I configure a per-model proxy in OpenClaw?
Add a proxy field under each entry in models[] in ~/.config/openclaw/openclaw.json5. Top-level sets defaults; per-model overrides. Priority: per-model > top-level > env vars > direct. Useful for routing OpenAI through a corporate proxy while Ollama runs direct on loopback.

Keep reading