Reverse Proxy
The Hub does not terminate TLS itself. To serve LeapMux over HTTPS, put a reverse proxy in front of the Hub. This page states what the proxy must carry, gives a configuration for five common proxies, and shows how to check the result.
What the proxy must carry
The Hub serves two kinds of traffic on one port:
| Traffic | Path | How the proxy forwards it |
|---|---|---|
| The Worker’s stream, web app live updates, and encrypted channels | /ws/ | HTTP/1.1 WebSocket upgrades that stay open for hours |
The web app, its API calls, Worker registration, and leapmux control | Every other path | HTTP/1.1 or HTTP/2 |
The Hub pings each WebSocket, including the Worker’s stream, every 10 seconds. Use an inactivity timeout of one minute or longer. Regular probes keep a healthy connection active through that timeout.
The proxy must also do these things:
- Forward WebSocket upgrades.
- Pass the
Hostheader, and setX-Forwarded-ForandX-Forwarded-Proto. - Serve LeapMux at the root of its own host name.
https://example.com/leapmux, and the Hub refuses such a public_url. Give LeapMux its own host name or subdomain.Configure the Hub
- Make the Hub listen on loopback only, so that clients can reach it only through the proxy. Set
listen: "127.0.0.1:4327"in the Hub’s configuration file; see Configuration. In Docker, publish the port on loopback with-p 127.0.0.1:4327:4327. - Set
public_urlto the external HTTPS URL:leapmux control admin settings set public_url "https://hub.example.com". - Enable secure cookies with
leapmux control admin settings set secure_cookies true. This signs out current sessions, so configure it during initial setup. - Trust the proxy’s address with
leapmux control admin settings set trusted_proxy_ranges '["127.0.0.1", "::1"]'. This example trusts a proxy on the same machine over IPv4 or IPv6. Use the actual address or range for a remote proxy. The Hub ignores the forwarding headers of a proxy that it does not trust. - Register each Worker with
--hubset to the same externalhttps://address. A Worker keeps using the URL that it registered with. Workers always initiate outbound connections, so they need no inbound ports of their own.
The Hub applies the three settings within about 30 seconds. No restart is needed. For the supported proxy address formats and forwarding headers, see Trusted reverse proxies.
cloudflare or cloudfront in trusted_proxy_ranges, those values trust the bundled ranges for the entire provider. Restrict access to your distribution or use authenticated origin requests. Configure every proxy to remove client-supplied forwarding headers or append only verified values. LeapMux does not download updated provider ranges automatically.Proxy configurations
Each configuration below does these things:
- It serves
hub.example.comon port 443, and redirects port 80 to it. - It reaches the Hub at
127.0.0.1:4327on the same machine. - It forwards all traffic to the Hub over HTTP/1.1, including WebSocket upgrades.
The repository’s task test-reverse-proxies harness verifies registration, connection, and transfers larger than 4 MiB before and after a 200-second stability window. It checks the web app’s event WebSocket for that window too. All five examples pass those checks. A separate HAProxy run verifies a 15-second inactivity timeout for 60 seconds.
Replace the host name, the certificate paths, and the Hub’s address with your own. The title of each file states where the file goes.
nginx
The configuration requires nginx 1.25.1 or later, for the http2 directive.
client_max_body_size 0 lets the Hub enforce its own limit on unary requests. nginx’s default limit of 1 MiB would reject bulk requests that the Hub accepts. The Worker’s stream uses a WebSocket upgrade.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name hub.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name hub.example.com;
ssl_certificate /etc/letsencrypt/live/hub.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hub.example.com/privkey.pem;
# The Hub enforces the size limit of each unary request. nginx's default
# of 1 MiB would reject bulk requests that the Hub allows.
client_max_body_size 0;
# The web app, API calls, and WebSocket upgrades, including the Worker stream.
location / {
proxy_pass http://127.0.0.1:4327;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Caddy
Caddy obtains and renews the certificate itself. One reverse_proxy forwards API calls and WebSocket upgrades to the Hub and sets the forwarding headers.
# Caddy obtains and renews the certificate for hub.example.com itself.
hub.example.com {
reverse_proxy 127.0.0.1:4327
}Traefik
The static configuration defines the entry points. Traefik’s default request-read timeout permits the WebSocket stream; no timeout override is needed.
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
file:
directory: /etc/traefik/dynamicThe dynamic configuration uses one router and one HTTP upstream for the web app, API calls, and WebSocket upgrades.
http:
routers:
# The web app, API calls, and all WebSocket upgrades.
leapmux:
rule: Host(`hub.example.com`)
entryPoints: [websecure]
service: leapmux
tls: {}
services:
leapmux:
loadBalancer:
servers:
- url: http://127.0.0.1:4327
tls:
certificates:
- certFile: /etc/traefik/certs/hub.example.com.crt
keyFile: /etc/traefik/certs/hub.example.com.keyTo obtain the certificate from Let’s Encrypt, define a certificate resolver in the static configuration, set it as tls.certResolver on the router, and remove the tls.certificates section. The Traefik documentation describes certificate resolvers.
HAProxy
alpn h2,http/1.1 on the bind line offers HTTP/2 to browsers. The HTTP/1.1 upstream carries API calls and WebSocket upgrades. The certificate file holds the certificate chain followed by the private key.
global
log stdout format raw local0
defaults
mode http
log global
option httplog
timeout connect 5s
# The Hub pings each WebSocket, the Worker's stream included, every
# 10 seconds. One minute of silence therefore means a dead peer.
timeout client 1m
timeout server 1m
timeout tunnel 1m
frontend leapmux-public
bind :80
bind :443 ssl crt /etc/haproxy/certs/hub.example.com.pem alpn h2,http/1.1
http-request redirect scheme https code 301 unless { ssl_fc }
http-request set-header X-Forwarded-Proto https
option forwardfor
# The web app, API calls, and all WebSocket upgrades.
default_backend leapmux-hub
backend leapmux-hub
server hub 127.0.0.1:4327Apache httpd
The configuration requires Apache httpd 2.4.47 or later, for upgrade=websocket. One HTTP upstream carries all traffic, including the Worker’s WebSocket. Enable the modules and the site on Debian or Ubuntu:
sudo a2enmod ssl http2 proxy proxy_http headers
sudo a2ensite leapmux
sudo systemctl reload apache2# Required modules: ssl http2 proxy proxy_http headers
<VirtualHost *:80>
ServerName hub.example.com
Redirect permanent / https://hub.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName hub.example.com
Protocols h2 http/1.1
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/hub.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/hub.example.com/privkey.pem
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
# The web app, API calls, and all WebSocket upgrades.
ProxyPass / http://127.0.0.1:4327/ upgrade=websocket
</VirtualHost>Check the setup
Check that the proxy serves the web app over HTTP/2:
curl -sI https://hub.example.com/ | head -1The first line is
HTTP/2 200.Register a Worker through the proxy, and start it. See Managing Workers.
leapmux worker register --hub https://hub.example.com --registration-key <key> leapmux worker startThe registration prints
Worker registered., and the Worker appears in the sidebar Workers section.
If a step fails, see A Worker cannot register or stay connected through a reverse proxy.