On this page
On this page
Remote access
Cloudflare Tunnel and Access
Run the Gateway on loopback, publish it through a Cloudflare Tunnel, and let Cloudflare
Access authenticate every request before it reaches OpenClaw. The Gateway keeps
gateway.bind: "loopback", so no port is exposed and no inbound firewall rule is
needed; cloudflared dials out from the host.
This is one supported remote-access topology alongside Tailscale and an SSH tunnel. Choose it when you want a stable public HTTPS URL and identity-provider SSO in front of the Control UI.
Before you begin
- A Cloudflare account with the zone for your hostname, and Cloudflare Zero Trust enabled.
cloudflaredinstalled on the Gateway host, and on any machine that will use the CLI.- A running Gateway on
127.0.0.1:18789withgateway.bind: "loopback". - Familiarity with trusted-proxy auth, which this topology uses.
How the pieces fit
Access authenticates the request and injects identity headers. The Gateway does not
re-authenticate the person or verify the Access JWT signature; it checks the trusted
proxy source and configured header presence, then trusts the user header. Because
allowLoopback also lets other local processes present those headers, keep the Gateway
port private to the host and run only trusted workloads there.
Step 1: Route the tunnel to loopback
Add an ingress rule mapping your hostname to the Gateway port, then run cloudflared
as a service on the Gateway host:
See Cloudflare's own documentation for creating the tunnel and DNS record.
Step 2: Protect the hostname with Access
Create an Access application for gateway.example with a policy that allows your
users. Note the two headers Access adds to authenticated requests, because the Gateway
consumes them in the next step:
cf-access-authenticated-user-email— the authenticated identity.cf-access-jwt-assertion— Access's signed assertion. OpenClaw checks only that this header is present and non-blank; it does not verify the JWT signature.
OIDC sign-in and existing people
For OIDC sign-in, configure an Access policy that admits the intended users through that identity provider. For example, use a signed role claim maintained by the provider. A GitHub organization policy applies to the GitHub identity provider; it does not grant access through a separate OIDC provider.
OpenClaw verifies the OIDC identity through Cloudflare Access's identity endpoint and requires its email to match the authenticated user header. It then resolves that email through the existing person profile. Using the same email retains the person's profile and role; a different email requires an existing linked alias to resolve to that person. GitHub sign-in continues to verify the immutable GitHub account ID. Failed identity verification does not fall back to email matching.
Keep the identity provider responsible for verifying email ownership. Creating an OpenClaw person profile does not grant access through Cloudflare Access.
Verified GitHub credit through OIDC
An OIDC provider can supply a verified GitHub account without changing the sign-in email or the account used to publish pull requests. This is optional and disabled until you explicitly trust one Access issuer, identity-provider ID, and claim name:
The issuer is the Access team origin without a trailing slash. providerId is
the selected integration's ID from Access, not its name or an OIDC user subject.
The provider must verify ownership of the GitHub account and bind it to the
verified sign-in email. Its ID token must contain a canonical positive
decimal-string account ID, such as "12345", within JavaScript's safe-integer
range. Configure Access to forward that exact custom OIDC claim.
OpenClaw reads it from oidc_fields in the authenticated
/cdn-cgi/access/get-identity response, or from custom when oidc_fields is
absent, then verifies the numeric account through GitHub to obtain its current
public login. A present oidc_fields container takes precedence: OpenClaw does
not retry custom if the preferred container lacks the claim or its value is
invalid. The identity-provider Test preview uses oidc_fields; inspect the
authenticated identity endpoint after a fresh sign-in to verify the response
OpenClaw consumes.
A missing or malformed optional claim, or an unselected issuer/provider, keeps ordinary email-only resolution. Malformed claims are never sent to GitHub or used to create an association or public credit. If the optional GitHub account lookup fails, OpenClaw resolves the authenticated email through the normal profile and access-policy checks. An existing authorized profile or valid email invitation can still sign in. A GitHub-only access requirement is not satisfied by an email fallback.
Lookup failure does not create a GitHub identity or public credit. An existing
verified matching association and saved co-author preference remain intact;
a later successful lookup can complete or refresh the link. This behavior
applies to both oidc_fields and custom. Invalid Access authentication,
mismatched principals, conflicting profile bindings for valid account claims,
and expired or revoked access still fail their normal checks.
Existing email profiles retain
their identity, role, and saved co-author preference. A conflicting GitHub account
does not automatically merge profiles or move the email; an administrator must
resolve it through the existing users.linkEmail operation. This also applies
to a first-time email claiming an account that already belongs to another
profile: link that email explicitly before it can inherit the profile's role.
Explicitly linked secondary accounts retain the profile's primary account for public credit.
See Gateway profiles and GitHub credit.
Step 3: Trust those headers in the Gateway
Set gateway.auth.mode to trusted-proxy and name the Access headers. allowLoopback
is required here: cloudflared connects from 127.0.0.1, and trusted-proxy auth
otherwise expects a non-loopback proxy.
Requiring cf-access-jwt-assertion adds a second presence check, not cryptographic
verification. A local process that can connect to the Gateway can submit both headers,
so do not treat this setting as a defense against untrusted local code. The security
boundary is the locked-down loopback port plus Cloudflare Access and the tunnel being
the only path for external traffic.
publicOrigin supplies external session links, agent link instructions, and the
browser-origin default when controlUi.allowedOrigins is omitted. You only need
an explicit allowlist for a different browser policy; that list replaces the
default rather than adding to it. For profile, role, GitHub, and operations setup,
see Deploy a team server.
Step 4: Decide how nodes and workers get in
Access protects every route on the hostname, including the ones nodes use. A node can authenticate to Access on every leg it needs — the join request, the main Gateway WebSocket, the worker socket, and worker transfers — so the recommended path exposes nothing publicly.
Recommended: give the node an Access service token. Add a Service Auth policy to the application, then on the node host:
openclaw connect persists these as env SecretRefs under
gateway.cloudflareAccess.clientId / clientSecret; see Node CLI. The only
cost is that the node needs those two values before the join command, so a join link is no
longer paste-and-go on its own.
Alternative: exempt the self-authenticating routes. Allow /j/* and
/__openclaw__/worker without Access identity, keeping WebSocket upgrade enabled on the
worker route. Both enforce their own short-lived credentials — a join code is single-use
with a TTL, rate-limited per IP, and answers failures with an opaque 404; worker admission
carries its own expiring credential. This keeps join links paste-and-go, at the cost of
making those two routes publicly reachable. Prefer the service token unless you need that
onboarding flow. See Nodes.
If you do neither, openclaw connect fails against the tunnel even though the browser
works, because the join request is redirected to the Access login page.
Step 5: Connect each client
Control UI. Open https://gateway.example and sign in through Access. With
trusted-proxy auth the Gateway maps your Access identity to an operator session.
If Access expires while a chat is open, the chat connection can remain active while new image and file requests require renewed website access. The Control UI first attempts automatic renewal through a hidden, sandboxed browser navigation. If your global Cloudflare Access session is still valid and your browser permits its cookies, Access can issue a fresh application cookie without another login. OpenClaw verifies access before retrying failed attachments, keeping your conversation and unsent draft open.
If renewal still requires sign-in, the Control UI opens one Sign in to continue loading content dialog. An expired global session, an identity-provider challenge, or blocked third-party cookies can require this manual step. Automatic renewal does not extend the session durations configured in Cloudflare Access. Choose Sign in, finish authentication in the new tab, and return to the conversation. Visible failed attachments retry after access is verified; the original conversation and unsent draft stay open. Check again repeats the access check, and Not now dismisses the prompt without interrupting the chat. Ordinary network failures and missing files do not trigger this dialog.
CLI and TUI. These do not carry browser cookies, so they present an Access token on
the WebSocket upgrade. Configure gateway.remote.edgeAuth as described in
Remote access, then run
cloudflared access login https://gateway.example once to cache a token.
Nodes. Follow the choice made in step 4.
Verify
Expect the TUI to reach wss://gateway.example and show connected. A first
connection may report device pairing required; approve it in the Control UI under
Settings → Devices, or run openclaw devices approve --latest on the Gateway host
to preview the request, then rerun the approval command it prints.
Reaching the Gateway's own pairing prompt is itself the proof that Access was satisfied — an unauthenticated request never gets that far.
Production readiness
- Keep
gateway.bind: "loopback". Binding wider re-exposes the Gateway beside the tunnel and bypasses Access entirely. - Keep
trustedProxieslimited to loopback. It is the list of addresses whose identity headers the Gateway will believe. trustedProxy.deviceAutoApprovecan pair devices automatically for Access-authenticated identities. It removes a manual approval step; enable it only when you accept that anyone who passes Access gets a paired device with the scopes you list.- Access tokens expire on the application's session duration. Expect CLI users to re-run
cloudflared access loginwhen their token lapses.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
gateway rejected websocket upgrade (HTTP 302) from the CLI or TUI |
Access intercepted the upgrade. Configure gateway.remote.edgeAuth; see Remote access. |
Browser works, openclaw connect fails |
Node routes are still behind Access. Apply one of the options in step 4. |
Exec provider ... exited with code 1 |
The exec secret provider runs with a scrubbed environment; cloudflared needs passEnv: ["HOME"] to read its cached token. |
secrets.providers.*.command must not be a symlink |
Point command at the resolved binary, not a package-manager symlink. |
| Gateway starts but every request is anonymous | allowLoopback is unset, so headers from the local cloudflared are ignored. |