Deploy gateway TLS
Put the gateway behind TLS; plaintext http:// breaks long streams.
The gateway never terminates TLS itself — it speaks plain HTTP on :8787 and is designed to
sit behind a TLS edge (a reverse proxy, an Ingress, a load balancer, or your platform's router).
How you terminate TLS depends on where you run it, but the rule is the same everywhere:
Set ANYRAY_GATEWAY_PUBLIC_URL to the https:// URL your users reach the gateway
at. The gateway reports it to your console, so enrollment links resolve to the secure URL
automatically. An http:// value (to anything but localhost) is flagged as insecure on
the setup page.
Pick your deployment
Self-host with the bundled Caddy edge + automatic Let's Encrypt.
KubernetesAn Ingress + cert-manager in front of the gateway Service.
Managed platformRailway / Render / Fly terminate TLS for you — just point the URL at it.
Cloud load balancerAWS ALB / Google Cloud LB with a managed (ACM) certificate.
Docker Compose
The stack ships a Caddy TLS edge behind the public profile — it provisions and renews a
Let's Encrypt certificate automatically. You need a domain (or subdomain) with a DNS A record
pointing at the host, and ports 80/443 open.
Create a DNS A record, e.g. gateway.example.com → your host's public IP.
In .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
docker compose --profile public up -d
Caddy comes up on :80/:443, gets the certificate, and proxies to the gateway. The console
and /admin stay in-network only.
Re-run anyray-connect --gateway https://gateway.example.com, or share fresh enrollment links
— once the gateway reports its https ANYRAY_GATEWAY_PUBLIC_URL, new links resolve to the
secure URL automatically. Once every tool is on https, close off any direct plaintext :8787
exposure.
Exposing the console over HTTPS (optional)
By default the console (compose port 3000) speaks plain HTTP and stays in-network — reach it
over an SSH tunnel. If you want the app.anyray.ai Billing app to link your
admins straight to it, give the console its own public hostname so the bundled Caddy edge
terminates TLS for it too:
Create another DNS A record, e.g. console.example.com → the same host IP.
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 so it re-reports the
URL). Leave both unset to keep the console in-network only.
The console hostname serves the admin console and the /admin, /public, and /log
endpoints — gated by admin auth (ANYRAY_ADMIN_TOKEN, or console SSO if enabled; the gateway
hostname keeps 403-ing them). Use a strong admin token, and prefer an SSH tunnel if you don't need
the Billing app hand-off.
Kubernetes
Terminate TLS at an Ingress with cert-manager (or your cluster's managed certificate). The
gateway runs as a normal Service on :8787; the Ingress is the only thing exposed.
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 }
Set on the gateway Deployment:
ANYRAY_GATEWAY_PUBLIC_URL=https://gateway.example.com
ANYRAY_TRUST_PROXY=true
Re-run anyray-connect --gateway https://gateway.example.com, or share fresh enrollment links
— once the gateway reports its https ANYRAY_GATEWAY_PUBLIC_URL, new links resolve to the
secure URL automatically. Once every tool is on https, close off any direct plaintext :8787
exposure.
/admin privateDon't expose /admin/*, /public/*, or /log/* through the public Ingress — keep the console and
admin API in-network (a separate internal Ingress or kubectl port-forward). The bundled Caddy
edge blocks them by default; on an Ingress you enforce it with path rules.
Managed platforms
Managed platforms terminate TLS for you and give the service a public
https:// hostname. There's nothing to provision — just don't expose :8787 directly, and set:
ANYRAY_GATEWAY_PUBLIC_URL=https://your-app.up.railway.app
ANYRAY_TRUST_PROXY=true
Then re-point users at the new URL: re-run anyray-connect --gateway <https-url> or share
fresh enrollment links, and once every tool is on https, close off any direct plaintext :8787
exposure.
Cloud load balancers
Terminate TLS at the load balancer with a managed certificate, targeting the gateway on :8787.
- AWS (ALB)
- Google Cloud (HTTPS LB)
Front the gateway with an Application Load Balancer: an HTTPS:443 listener with an ACM
certificate, forwarding to a target group on the gateway's :8787. Lock the gateway's
security group so :8787 only accepts traffic from the ALB.
Use an external HTTPS load balancer with a Google-managed certificate and a backend service
pointing at the gateway. Restrict the gateway so only the LB's ranges reach :8787.
ANYRAY_GATEWAY_PUBLIC_URL=https://gateway.example.com
ANYRAY_TRUST_PROXY=true
Then re-point users at the new URL: re-run anyray-connect --gateway https://gateway.example.com
or share fresh enrollment links, and once every tool is on https, close off any direct plaintext
:8787 exposure.