This guide shows how to reach a home or office Aegis instance from anywhere
without exposing port 9100 directly to the public internet.
For remote-access deployments, explicitly keep Aegis on
AEGIS_HOST=127.0.0.1 and let a tunnel or reverse proxy reach loopback.
Aegis does not ship its own ingress layer, and the dashboard uses the same API
token model as the REST API, so remote access is always a two-layer problem:
- Transport β get traffic from the remote client to the host over TLS without opening inbound firewall ports.
- Authentication β protect the API token and, for anything beyond a private tailnet, add an upstream identity gate in front of the dashboard.
This guide covers three transport options and the security posture each one expects.
| Option | Best for | Public URL | TLS | Identity layer | Cost |
|---|---|---|---|---|---|
| Tailscale | Solo dev on trusted devices | No | Auto | Tailnet users + ACLs | Free personal |
| Cloudflare Tunnel | Stable HTTPS URL for a small team | Yes | Auto | Cloudflare Access | Free tier |
| ngrok | Short demos or temporary access | Yes | Auto | Paid plans only | Free + paid |
Recommendation: use Tailscale unless you need a public hostname or need to share access with people who are not on your tailnet. Reach for Cloudflare Tunnel when you do. Use ngrok for demos only.
Across all three options, keep Aegis listening on loopback:
AEGIS_HOST=127.0.0.1
AEGIS_PORT=9100That keeps the local LAN out of scope and makes the tunnel the only ingress path you have to secure.
Tailscale is the best fit for a solo developer: it gives you a private WireGuard mesh between your own devices, plus a tailnet-only HTTPS endpoint without opening the service to the public internet.
Install the Tailscale app on the Aegis host and on each client device, then sign in to the same tailnet. On a Linux host:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --https=443 http://127.0.0.1:9100
tailscale serve statustailscale serve publishes the local Aegis server to your tailnet-only
https://<hostname>.<tailnet>.ts.net/ domain while Aegis itself stays bound
to 127.0.0.1.
curl https://<hostname>.<tailnet>.ts.net/v1/health
curl -H "Authorization: Bearer $AEGIS_AUTH_TOKEN" \
https://<hostname>.<tailnet>.ts.net/v1/sessionsThe dashboard is available at
https://<hostname>.<tailnet>.ts.net/dashboard/.
- Enable MagicDNS and HTTPS certificates in the Tailscale admin.
- Use ACLs / grants so only your own devices or a small admin group can reach the service.
- Keep using
AEGIS_HOST=127.0.0.1; lettailscale serveproxy to loopback. - Do not use Tailscale Funnel unless you intentionally want public internet exposure and have reviewed the security section below first.
cloudflared opens an outbound connection from the host to Cloudflare's edge.
You get a stable https://aegis.example.com URL backed by Cloudflare TLS with
no inbound firewall changes.
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
-o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
cloudflared tunnel login
cloudflared tunnel create aegisCreate ~/.cloudflared/config.yml:
tunnel: <tunnel-id>
credentials-file: /home/you/.cloudflared/<tunnel-id>.json
ingress:
- hostname: aegis.example.com
service: http://127.0.0.1:9100
- service: http_status:404cloudflared tunnel route dns aegis aegis.example.com
sudo cloudflared service installWithout Access, anyone who knows the URL only needs a valid Aegis token. Add a Zero Trust Access application so the tunnel is gated before traffic reaches Aegis:
- Zero Trust dashboard β Access β Applications β add
aegis.example.com. - Policy: Require email domain, one-time PIN, or your SSO provider.
- Keep session duration short (for example 1β8 hours).
Cloudflare Access then becomes the first factor and the Aegis API token remains the second.
curl https://aegis.example.com/v1/health
curl -H "Authorization: Bearer $AEGIS_AUTH_TOKEN" \
https://aegis.example.com/v1/sessionsngrok is best for short-lived demos. On the free plan the URL changes on every restart, and public protection features are limited. Treat ngrok as temporary access, not a persistent deployment plan.
npm install -g ngrok
ngrok config add-authtoken <your-token>
ngrok http 127.0.0.1:9100ngrok prints a https://<random>.ngrok-free.app URL that forwards to
http://127.0.0.1:9100.
If you need ngrok up for more than a quick demo:
- Use a reserved domain (paid plan) so the URL is stable and auditable.
- Add OAuth or another auth policy at the tunnel edge β do not rely only on the Aegis token over a public demo URL.
- Rotate the token when the demo ends.
Example ngrok traffic policy:
# oauth-policy.yml
on_http_request:
- actions:
- type: oauth
config:
provider: google
- expressions:
- "!(actions.ngrok.oauth.identity.email.endsWith('@example.com'))"
actions:
- type: denyngrok http 127.0.0.1:9100 \
--domain=aegis-demo.ngrok.app \
--traffic-policy-file=oauth-policy.ymlRemote access amplifies weaknesses that may be tolerable on localhost. Treat
each item below as a gate, not a suggestion.
AEGIS_AUTH_TOKENis the bootstrap credential for the API and dashboard. A browser, phone, or laptop that stores it should be treated as a privileged admin endpoint.- Prefer the least-privileged API key that still fits the job:
viewerfor read-only monitoringoperatorfor normal session controladminonly for key management and full control
- Session ownership still applies to non-master keys, so a scoped key is safer than copying the same full-power token to every remote client.
- Generate strong secrets (for example
openssl rand -hex 32), keep them out of the repo, and rotate them whenever a device leaves your control. - Do not send tokens through the tunnel itself. Share them out-of-band with a password manager or secret store.
Aegis is CORS-disabled by default. The bundled dashboard calls the API on the same origin, so tunnels do not need CORS by themselves.
-
If you put a separate web app in front of Aegis, set
CORS_ORIGINto an explicit allow-list:CORS_ORIGIN="https://openclaw.example.com,https://admin.example.com" -
CORS_ORIGIN=*is rejected at startup. Keep it that way for remote access; wildcard browser access plus a cached token is an unnecessary foot-gun.
The dashboard is served with:
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data:; connect-src 'self' ws: wss: https://registry.npmjs.org
- Same-origin tunnels (Tailscale, Cloudflare Tunnel, ngrok) work without changing the policy.
- If you add analytics, CDNs, or error-reporting scripts, update the CSP in Aegis itself instead of rewriting headers at the tunnel edge.
- Do not disable CSP to "fix" remote access. Mixed-content or wrong-origin issues are almost always better solved by fixing the tunnel URL.
The dashboard has a login page, but it is only a thin wrapper around the same API token model as the REST API:
/dashboard/must stay publicly reachable enough to load the static login page, then the browser verifies the token via/v1/auth/verify.- After sign-in, the token is stored in the browser. Use trusted devices only, and clear stored tokens when a browser profile is no longer trusted.
- There are no separate dashboard users, passwords, or MFA controls inside
Aegis today, so upstream identity matters:
- Tailscale: rely on tailnet membership plus ACLs.
- Cloudflare Tunnel: require Cloudflare Access.
- ngrok: require OAuth or keep it demo-only.
- Review
GET /v1/auditafter a suspected leak, but rotate the token first.
-
Keep
AEGIS_HOST=127.0.0.1and let the tunnel proxy to loopback. -
Avoid binding to
0.0.0.0just because you added a tunnel. Doing both means the tunnel and your local network can reach Aegis. -
Verify the listener on the host:
ss -tlnp | grep 9100You want to see
127.0.0.1:9100, not0.0.0.0:9100.
Before leaving a remote-access setup running:
-
AEGIS_AUTH_TOKENor any API key is at least 32 random bytes and stored outside the repo. -
AEGIS_HOST=127.0.0.1is set explicitly on the host. - The tunnel has its own identity layer enabled (tailnet ACLs, Cloudflare Access, or ngrok OAuth).
-
CORS_ORIGINis unset or a strict allow-list β never*. -
/v1/healthworks through the tunnel and privileged endpoints still return401without a token. - Browsers allowed to use
/dashboard/are trusted, because the token is stored locally after sign-in. - You have a token-rotation runbook for lost devices, expired demos, and collaborator removal.
- Deployment Guide β systemd, Docker, reverse proxy, safer host binding.
- Enterprise Deployment β auth, rate limiting, hardening.
- ADR-0023 β why Aegis is a self-hosted control plane and not a SaaS.