The engine serves plain HTTP on one port (SYNTHIGY_SERVER_PORT, default 7887), so
an internet-facing instance sits behind nginx, Caddy, Traefik or a load balancer.
Copy-paste configs are at the bottom.
SYNTHIGY_SERVER_HOST=127.0.0.1 # reachable only through the proxy
SYNTHIGY_IAM_ROOT_URL=https://synthigy.example.com
SYNTHIGY_SERVER_TRUST_PROXY=127.0.0.1/32,::1/128
Secure; over plain http:// the login
page accepts the password and comes straight back.SYNTHIGY_IAM_ROOT_URL is the public origin: token issuer and base of every
login link. Unset, it is taken from X-Forwarded-Host/-Proto per request, which
is wrong behind a load balancer, or from Host when the proxy isn't trusted. The
portal flags it until set.SYNTHIGY_SERVER_TRUST_PROXY lists your proxies (IPs or CIDRs). Only requests
from them have X-Forwarded-For, -Host and -Proto believed; from anyone else
those headers are dropped. Synthigy reads X-Forwarded-For from the
right and skips your proxies, so a client can't fake its address. It can only
find the address the first hop recorded: behind a load balancer, the proxy on
the instance must keep that record, not overwrite it — see
Behind an AWS load balancer. In a
container network, list the proxy's subnet.X-Request-Id from a trusted proxy becomes the request id./data/events and /console/live/* (server-sent
events, keepalive every 15–20s): buffering and compression off, read timeout well
above that. A buffering proxy looks like a frozen console. There are no WebSockets.StripPrefix.GET /health, 200 while the node can serve, 503 when it can't
(OPERATIONS.md).X-Forwarded-Proto and X-Forwarded-Host.ssh -L 7888:127.0.0.1:7888 <host>.The configs below are for a proxy that faces the internet itself. Behind a load balancer, see the last section.
Write proxy_pass http://synthigy; with no path after the host: a trailing /
makes nginx strip the location prefix, and routes 404.
upstream synthigy {
server 127.0.0.1:7887;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name synthigy.example.com;
return 301 https://$host$request_uri;
}
server {
# works on nginx 1.14+; on 1.25.1+ you may use `listen 443 ssl;` + `http2 on;`
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name synthigy.example.com;
ssl_certificate /etc/letsencrypt/live/synthigy.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/synthigy.example.com/privkey.pem;
client_max_body_size 64m;
proxy_http_version 1.1;
# needed by `keepalive` in the upstream block
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
location / {
proxy_pass http://synthigy;
proxy_read_timeout 75s;
}
location /console/assets/ {
proxy_pass http://synthigy;
proxy_cache_valid 200 1h;
}
location = /data/events {
proxy_pass http://synthigy;
proxy_buffering off;
proxy_cache off;
gzip off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
location ^~ /console/live/ {
proxy_pass http://synthigy;
proxy_buffering off;
proxy_cache off;
gzip off;
proxy_read_timeout 5m;
proxy_send_timeout 5m;
}
}
synthigy.example.com {
@sse path /data/events /console/live/*
@rest not path /data/events /console/live/*
encode @rest zstd gzip
reverse_proxy @sse 127.0.0.1:7887 {
header_up X-Forwarded-For {remote_host}
flush_interval -1
transport http {
read_timeout 0
compression off
}
}
reverse_proxy 127.0.0.1:7887 {
header_up X-Forwarded-For {remote_host}
}
}
Caddy obtains the certificate itself. {remote_host} replaces the
client-supplied X-Forwarded-For rather than appending to it — right when Caddy
is the edge, wrong behind a load balancer.
Static (traefik.yml) — with no trustedIPs, Traefik drops a client-supplied
X-Forwarded-For and writes the real peer address itself, which is what
Synthigy needs. Add trustedIPs only for a load balancer in front of Traefik.
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
forwardedHeaders:
insecure: false
certificatesResolvers:
le:
acme:
email: ops@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
file:
filename: /etc/traefik/dynamic.yml
watch: true
Dynamic (dynamic.yml) — one router for the whole host, no path rewriting:
http:
routers:
synthigy:
rule: "Host(`synthigy.example.com`)"
entryPoints:
- websecure
service: synthigy
tls:
certResolver: le
services:
synthigy:
loadBalancer:
servers:
- url: "http://127.0.0.1:7887"
passHostHeader: true
responseForwarding:
flushInterval: "100ms"
As Docker labels instead of the file provider:
labels:
- "traefik.enable=true"
- "traefik.http.routers.synthigy.rule=Host(`synthigy.example.com`)"
- "traefik.http.routers.synthigy.entrypoints=websecure"
- "traefik.http.routers.synthigy.tls.certresolver=le"
- "traefik.http.services.synthigy.loadbalancer.server.port=7887"
- "traefik.http.services.synthigy.loadbalancer.responseforwarding.flushinterval=100ms"
client → ALB (TLS) → proxy on the instance, port 80 → engine on 127.0.0.1:7887
Each hop sees only the machine connected to it. The ALB records the client in
X-Forwarded-For; the proxy on the instance sees the ALB. A proxy that overwrites
the header with its own peer, as the configs above do, makes every request look
like it came from the ALB. Make the proxy trust the ALB and take the client from
the header instead; SYNTHIGY_SERVER_TRUST_PROXY then stays at loopback.
ALB
GET /health. The default 60s idle
timeout is above the 15–20s event-stream keepalive.Engine: the settings at the top. SYNTHIGY_IAM_ROOT_URL is not optional here —
the proxy receives plain HTTP, so anything derived from the request says http://.
nginx — replaces both server blocks of the nginx config; keep its upstream
and location blocks. Needs ngx_http_realip_module, included in the
distribution packages.
server {
listen 80;
server_name synthigy.example.com;
# one line per subnet the ALB is attached to
set_real_ip_from 10.0.1.0/24;
set_real_ip_from 10.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
client_max_body_size 64m;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
# $remote_addr is the client once realip has run
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
# location blocks as in the nginx config
}
Caddy 2.8+
{
servers {
trusted_proxies static 10.0.1.0/24 10.0.2.0/24
trusted_proxies_strict
}
}
http://synthigy.example.com {
@sse path /data/events /console/live/*
@rest not path /data/events /console/live/*
encode @rest zstd gzip
reverse_proxy @sse 127.0.0.1:7887 {
header_up X-Forwarded-For {client_ip}
flush_interval -1
transport http {
read_timeout 0
compression off
}
}
reverse_proxy 127.0.0.1:7887 {
header_up X-Forwarded-For {client_ip}
}
}
trusted_proxies_strict makes Caddy read the header from the right; without it
Caddy takes the leftmost entry, which the client wrote. http:// in the site
address leaves TLS to the ALB, and the ALB's X-Forwarded-Proto: https passes
through because the ALB is trusted.
Traefik: set forwardedHeaders.trustedIPs to the ALB subnets on the entry
point the ALB targets, and drop the web → websecure redirect (the ALB
redirects). Traefik keeps the ALB's entries and appends its own peer rather than
resolving the client, so add the ALB subnets to SYNTHIGY_SERVER_TRUST_PROXY
as well.
Check it: request log lines carry ip, the resolved client. Behind a
correctly configured ALB it is your public address, never a 10.x one.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |