GoRunner docs
Search or askSearch⌘K

Behind your own proxy

Put your own load balancer, reverse proxy or CDN in front of GoRunner.

10 min · Self-hosted

Your proxy holds the certificate and passes every request to GoRunner on a local port. No Caddy runs. On Kubernetes, your ingress plays this part. See The ingress.

1.Run the setup in own-proxy mode

In the install folder, give the address people open and where your proxy reaches GoRunner.

--behind-proxy
The https address your proxy serves.
--app-port
Where your proxy connects. 127.0.0.1:8080, the default, suits a proxy on this server. Never an address the internet can reach: this port is plain HTTP.
--trusted-proxies
Required for a proxy on another machine: its own address or subnet.

Without flags, ./setup.sh asks: under “The way in”, choose 2. A running installation first asks “Change any of them?”: answer y.

bash
# a proxy on this server
./setup.sh \
  --behind-proxy https://automations.example.com \
  --app-port 127.0.0.1:8080
bash
# a load balancer on another machine
./setup.sh \
  --behind-proxy https://automations.example.com \
  --app-port 10.0.1.5:8080 \
  --trusted-proxies 10.0.1.0/24

2.Point your proxy at GoRunner

Whatever the proxy, it must:

Pass every path
/, /api, /embed, /embed.js, /embed-sdk, /mcp, /oauth and /.well-known, to one place. /api holds webhooks and hosted forms too; /mcp, /oauth and /.well-known/oauth-… connect AI apps.
Name the caller
Keep Host, and set X-Forwarded-For to the connecting address, replacing what the caller sent.
Stream
No response buffering, and at least 60 seconds of quiet; an hour is safe. GoRunner sends a keep-alive every 25 seconds.
Take 6 MiB
GoRunner takes request bodies up to 5 MiB. nginx refuses over 1 MB unless raised.
Add no security headers
No X-Frame-Options or Content-Security-Policy. Strict-Transport-Security is yours to add.
Compress nothing it must hold back
GoRunner compresses the dashboard’s files itself. Compressing API answers is fine, but never by buffering a stream.

A health check can ask GET /api/ready: 200 when GoRunner reaches its database and Redis. See Health checks.

An AWS Application Load Balancer?
Target group
HTTP, the --app-port port on the server’s private address, health check /api/ready, success code 200.
Listener
HTTPS 443 with your certificate from ACM.
Idle timeout
The default 60 seconds works. Never lower it.
Trusted ranges
The subnets the load balancer runs in, such as 10.0.1.0/24 10.0.2.0/24.

It streams, takes any body size and adds X-Forwarded-For itself. Open the server’s port to the load balancer only.

upstream gorunner {
    server 127.0.0.1:8080;
    keepalive 16;
}

server {
    listen 443 ssl;
    http2 on;
    server_name automations.example.com;
    ssl_certificate     /etc/ssl/automations.example.com.crt;
    ssl_certificate_key /etc/ssl/automations.example.com.key;

    client_max_body_size 6m;

    location / {
        proxy_pass http://gorunner;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 1h;
    }
}

Behind a CDN, let nginx learn the caller first: set_real_ip_from <its range>; and real_ip_header X-Forwarded-For;. Traefik replaces forwarding headers a caller sent, unless the caller is in its entry point’s forwardedHeaders.trustedIPs.

3.Check it

  1. Open your address. The sign-in page appears, with your proxy’s certificate.
  2. Run a workflow from the editor. Its steps light up one by one: streaming works.
  3. Open Platform › Audit log. Your sign-in shows your computer’s address, not the proxy’s.
bash
curl -sS http://127.0.0.1:8080/api/ready

The address ranges to trust

Sign-in limits, an embed key’s address list and the audit log all need the caller’s real address. GoRunner reads X-Forwarded-For only from a connection it trusts, from the right, and takes the first address it doesn’t trust. It then drops Forwarded and True-Client-IP, and overwrites X-Real-IP and X-Forwarded-For with that address.

Your proxyTrust
On this server, --app-port on 127.0.0.1Nothing to give. The setup writes gateway, which only programs on this server can use.
On another machineIts own address or subnet. Required.
A CDN in front of your proxyLet your proxy learn the caller from it. Or add its range: on this server, it adds to gateway.

Give ranges separated by spaces or commas. The setup refuses private_ranges: it would trust every container on the server, workflow steps included. A range covering every address stops GoRunner from starting.

Warning A trusted range is believed about who is calling. Trust one that ordinary callers can reach, and they get past the sign-in limits and an API key’s address list, and into the audit log under another address.

A proxy in front of automatic HTTPS?

Name it with --trusted-proxies; on this server, with Caddy’s ports on 127.0.0.1, that is gateway. Caddy never trusts a range holding one of Docker’s networks, so private_ranges is refused there too. Name an IPv6 proxy by its exact address.

For Embed

Embed needs sign-in on, a license that includes Embedding, and one https address: an https page can’t frame an http installation. Pass /embed/ untouched: no cache, no framing header of your own. Without the license or sign-in, /embed/ answers 404.

/embed/
The embed document, only to your allowed websites. Its frame-ancestors names only the page it was opened for, with Cache-Control: no-store.
/embed.js
The SDK, always the current version: no-cache with an ETag.
/embed-sdk/<version>/embed.js
One version, never changed, cached for a year. Pin it with Subresource Integrity.
/api/embed/v1/…
The embed API and the frame’s sign-in.

GoRunner decides on every load, from the installation’s and each customer’s allowed websites; they never appear in a header. /embed.html is always 404. The SDK’s files answer Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin. The rest of the dashboard sends X-Frame-Options: SAMEORIGIN, the AI app consent screen DENY. See Set up Embed.

Set up behind a proxy with v2026.10.0 or v2026.10.1?

It keeps GoRunner’s Caddy between your proxy and the app, on the same port. To connect directly, run ./setup.sh, answer y to “Change any of them?”, and choose 2 under “The way in”. Then point your proxy at the address it gives.

A proxy that asks GET /api/embed/frame-policy itself must add ?parent=<the embed document’s parent> from v2026.10.4. Pointing it at the app as above needs none of that.

Checked against 12d85b3b