How-to
Reverse proxy
Put ScalWS in front of an HTTP application, send an /api path to a second upstream, and
control the Host header and WebSockets.
Prerequisites
- ScalWS installed, with
include: sites.d/*.yamlin/etc/scalws/scalws.yaml. - The upstream applications running and reachable from the server, for example a web
front end on
127.0.0.1:3000and an API on127.0.0.1:4000or a Unix socket.
Upstreams are spoken to over HTTP/1.1, as http://host[:port], https://host[:port] or
unix:/absolute/path. Works under both scalws-controller and scalwsd.service.
The site file
sudo scalwsctl site add example.com --proxy http://127.0.0.1:3000 --www aliasThen add the API route:
# /etc/scalws/sites.d/example.com.yaml
domain: example.com
www: alias
proxy: http://127.0.0.1:3000
routes:
- path: /api
runtime:
type: proxy
upstream: "http://127.0.0.1:4000"
response_timeout: 120s
- path: /healthz
runtime: {type: respond, body: "ok\n"}
https_redirect: trueproxy: the default upstream, as a URL or as a map of settings.routes[].path:/apimatches/apiand everything below it on whole segments (/api/users, not/apikeys). The longest matching prefix wins.- The path is not stripped: the API receives
/api/users, not/users. An upstream URL with a path (http://127.0.0.1:4000/v1) is refused. If the API expects paths without the prefix, set the route’srewrite(rewrite: {prefix: /}sends/api/usersas/users), or configure the prefix in the application. response_timeout: time for the upstream to send response headers (default60s);connect_timeoutdefaults to5s.
The Host header: preserve_host
preserve_host is true by default:
the upstream sees the client’s Host (example.com). Set it to false when the upstream
expects its own name, for example a virtual host on another server or a hosted service:
domain: example.com
proxy:
upstream: "https://app.example.org"
preserve_host: falseWith false the request carries Host: app.example.org. For https:// upstreams the
host is also the TLS server name; tls_ca_file
adds a CA bundle for a private CA.
Headers the upstream gets:
X-Forwarded-For(the client address) andX-Forwarded-Proto(httporhttps), set by ScalWS. Values the client sent inForwarded,X-Forwarded-*andX-Real-IPare discarded.X-Request-Id, the server’s request id.- Hop-by-hop headers are removed in both directions.
WebSockets
websocket is true by default: HTTP/1.1
Upgrade: websocket requests are tunnelled to the upstream on a dedicated connection.
Nothing to configure for a typical application. Turn it off on a route that must never
upgrade:
domain: example.com
proxy: http://127.0.0.1:3000
routes:
- path: /api
runtime: {type: proxy, upstream: "unix:/run/api/api.sock", websocket: false}Server-wide limits apply to all tunnels:
server.limits.max_upgraded_connections
(default 10000, beyond it 503) and
server.limits.upgraded_idle_timeout
(default 300s without traffic in either direction). WebSockets over HTTP/3 are not
supported.
Apply and check
sudo scalwsctl check
sudo scalws-controller reload # or: sudo scalwsctl reload under scalwsd.service
curl -sI -H 'Host: example.com' http://127.0.0.1/ # front end
curl -sI -H 'Host: example.com' http://127.0.0.1/api/ping # API
curl -s -H 'Host: example.com' http://127.0.0.1/healthz # okA WebSocket handshake through the proxy answers 101 Switching Protocols when the
upstream accepts it at that path (/socket here):
curl -si -H 'Host: example.com' -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
http://127.0.0.1/socket | head -1Common problems
- 502 Bad Gateway: the upstream refused the connection or sent invalid HTTP. The log
shows
upstream request failedwithreasonconnectorprotocol. Test the upstream withcurlfrom the server. - 504 Gateway Timeout: no response headers within
response_timeout. Raise it on the route that needs it. - The application builds wrong URLs or redirects to the upstream address: it gets the
upstream’s name in
Host; keeppreserve_host: true, or make the application useX-Forwarded-Protofor the scheme. - The API gets 404 for every path: it expects paths without
/api; the prefix is not stripped unless the route setsrewrite: {prefix: /}. runtime.upstream: ...inscalwsctl check: the upstream has a path, user information or port 0.- WebSockets fail:
websocket: falseon that route, or themax_upgraded_connectionslimit is reached. - A Unix socket upstream gives
Permission denied: userscalwsmust be able to connect to the socket.
See also: applications: proxy, routes, troubleshooting.