Skip to main content

Gateway TLS

The gateway speaks plain HTTP on :8787 and never terminates TLS. Put a TLS edge in front of it; plaintext http:// breaks long streams.

The one invariant

Set ANYRAY_GATEWAY_PUBLIC_URL to the https:// URL users reach. Enrollment links then use it. An http:// value (except loopback or .local) is flagged as insecure on the enrollment page.

Every recipe below also sets ANYRAY_TRUST_PROXY=true, and ends with Re-point users.

How per-IP limits find the client

With ANYRAY_TRUST_PROXY=true, per-IP limits key on the first X-Forwarded-For entry. A second rule is measured on this release and not enforced yet. It reads the header from the right and skips private-network hops. On Railway, whose edge replaces the header, it keeps the first entry. Behind Google's load balancer it skips the address the balancer appends after the client's. GET /admin/health reports how often it would choose a different key, under clientIpShadow. Client behavior does not change until a later release switches to it.

Docker Compose​

The public profile ships a Caddy edge that gets and renews a Let's Encrypt certificate. You need a DNS A record to the host and ports 80/443 open.

1
Point a domain at the host

DNS A record, e.g. gateway.example.com → the host's public IP.

2
Configure the edge
.env
ANYRAY_PUBLIC_DOMAIN=gateway.example.com # Caddy provisions a cert for this
ANYRAY_GATEWAY_PUBLIC_URL=https://gateway.example.com
ANYRAY_GATEWAY_BIND=127.0.0.1 # so :8787 isn't reachable, bypassing TLS
ANYRAY_TRUST_PROXY=true # trust X-Forwarded-* from Caddy
ANYRAY_HSTS=true # emit Strict-Transport-Security, safe once TLS fronts it
3
Start with the public profile
docker compose --profile public up -d

The console and /admin stay in-network.

Expose the console over HTTPS (optional)

The console (port 3000) stays in-network by default; reach it over an SSH tunnel. To let the app.anyray.ai Billing App link admins to it, add a second DNS A record to the same host, then:

.env
ANYRAY_CONSOLE_DOMAIN=console.example.com # Caddy provisions a cert for this
ANYRAY_CONSOLE_PUBLIC_URL=https://console.example.com # reported home, so the Billing App links here

Re-run docker compose --profile public up -d and restart the gateway.

This exposes the admin surface to the internet

The console hostname serves the console and /admin, /public, and /log, behind admin auth (ANYRAY_ADMIN_TOKEN, or console SSO). The gateway hostname keeps returning 403 for them. Use a strong admin token.

Kubernetes​

Terminate TLS at an Ingress with cert-manager (or a managed certificate). Expose only the Ingress.

gateway-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: anyray-gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [gateway.example.com]
secretName: anyray-gateway-tls
rules:
- host: gateway.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: anyray-gateway
port: { number: 8787 }
Gateway Deployment env
ANYRAY_GATEWAY_PUBLIC_URL=https://gateway.example.com
ANYRAY_TRUST_PROXY=true
Keep /admin private

Do not route /admin/*, /public/*, or /log/* through the public Ingress. Use an internal Ingress or kubectl port-forward. Caddy blocks them by default; on an Ingress, path rules do.

Managed platforms​

Railway, Render, and Fly terminate TLS and give an https:// hostname. Do not expose :8787 directly.

ANYRAY_GATEWAY_PUBLIC_URL=https://your-app.up.railway.app
ANYRAY_TRUST_PROXY=true

Cloud load balancers​

CloudTLS edgeLock down :8787 to
AWSALB, HTTPS:443 listener, ACM certificate, target group on :8787The ALB's security group
Google CloudExternal HTTPS LB, Google-managed certificate, backend service to the gatewayThe LB's ranges
ANYRAY_GATEWAY_PUBLIC_URL=https://gateway.example.com
ANYRAY_TRUST_PROXY=true

A certificate from your company's CA​

If the edge serves a certificate signed by your company's own certificate authority, every machine must trust that root.

1
Put the company root in each machine's OS trust store

Push it with MDM or Group Policy.

2
Install Claude Code with the native installer

The native build reads the OS trust store.

3
Check a machine

After you re-point it, run anyray-connect doctor. Under Gateway it prints:

Certificate: trusted by this machine's certificate store (company root: Example Corp Root CA).

When an older npm install of Claude Code cannot read the OS store, Connect points it at the company root it found there; it never trusts a root the machine does not. Every line doctor can print is in Gateway certificate.

A publicly trusted certificate on the edge hostname avoids all of this.

Re-point users​

Run anyray-connect --gateway https://gateway.example.com, or share new enrollment links. When every tool is on https, close any direct plaintext :8787 exposure.