TLS / SSL¶
Pylon listens on plain ws:// and HTTP by default. Two approaches are available
for adding TLS — choose one based on your deployment topology.
Terminate TLS at a dedicated proxy and forward plain HTTP/WebSocket to pylon on
its internal port (default 7000). This is the standard approach for production,
cloud load balancers, and Kubernetes.
Advantages: the proxy handles certificate provisioning and renewal, cipher negotiation, and HTTP/2 multiplexing without any changes to the pylon process. Pylon keeps a lower per-connection memory footprint than native TLS mode.
Caddy — automatic HTTPS¶
Caddy provisions and renews certificates via Let's Encrypt automatically. It proxies WebSocket Upgrade frames transparently with no special directives needed.
Requirements:
- A real, publicly resolvable domain name.
- Ports 80 and 443 reachable from the internet (ACME HTTP-01 challenge).
Use the annotated example as your starting point:
deploy/tls/Caddyfile.example contains the full configuration including an
optional block to restrict /metrics to internal networks.
nginx — manual certificate management¶
Use deploy/tls/nginx.conf.example as your starting point. Key points:
-
Obtain a certificate first:
Certbot writes certificates to/etc/letsencrypt/live/<domain>/and configures automatic renewal. -
WebSocket Upgrade headers — the most common misconfiguration. These three directives are required in every
Without them, nginx will not upgrade the HTTP connection to a WebSocket, and clients will get an HTTP 101 that immediately drops.locationblock that proxies to pylon: -
Long timeouts. The Pusher client sends a heartbeat every 120 s. nginx's default
proxy_read_timeoutof 60 s silently kills idle connections. Set both read and send timeouts well above the heartbeat period: -
Multiple upstream nodes. Add each pylon node to the
upstream pylon {}block. nginx round-robins requests; the redis adapter (PYLON_ADAPTER=redis) keeps cluster state consistent across nodes.
Kubernetes — terminate at Ingress¶
For Kubernetes deployments, terminate TLS at the Ingress controller using cert-manager. The pylon pods and Service stay on plain HTTP — no changes to the pylon Deployment are needed.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pylon
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# Raise read timeout above the Pusher heartbeat period (120 s).
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Required for WebSocket upgrade on nginx-ingress:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
spec:
ingressClassName: nginx
tls:
- hosts:
- your.domain.example
secretName: pylon-tls
rules:
- host: your.domain.example
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pylon
port:
number: 7000
cert-manager creates the pylon-tls Secret and renews it automatically.
Pylon can serve wss:// and the REST API on the same port directly, without
a proxy in front. This is suitable for single-node deployments or environments
where adding a proxy layer is not practical.
Enabling native TLS¶
Set both environment variables to PEM files:
Both variables must be set together, or both must be unset. Setting only one is a fatal configuration error — pylon will refuse to start and print which variable is missing.
| Variable | Required | Description |
|---|---|---|
PYLON_TLS_CERT |
Yes (with KEY) | Path to the PEM certificate chain (leaf first, then intermediates). |
PYLON_TLS_KEY |
Yes (with CERT) | Path to the PEM private key (PKCS#8, RSA, or EC). |
PYLON_TLS_CA |
No | Path to a PEM CA certificate. When set, enables mTLS — every client must present a valid certificate signed by this CA. |
mTLS (mutual TLS)¶
To require client certificates, additionally set:
Pylon will build a WebPkiClientVerifier from the CA and reject any connection
that does not present a valid client certificate signed by that CA. This is
useful for server-to-server scenarios where the client pool is controlled.
Memory cost¶
Native TLS increases per-connection memory relative to plain mode because rustls allocates send and receive buffers per TLS session (typically 32–64 KiB per connection). For deployments targeting millions of concurrent connections, the reverse-proxy approach is preferred — the TLS overhead lives in the proxy process rather than pylon, where connection density is maximised.
Protecting /metrics¶
/metrics exposes connection counts, Redis lag, and memory statistics. It should
not be publicly reachable.
- Caddy: uncomment the
@metrics_publicmatcher block indeploy/tls/Caddyfile.exampleand adjust the CIDR. - nginx: uncomment the
location /metrics { allow … ; deny all; }block indeploy/tls/nginx.conf.example. - Kubernetes: add a
whitelist-source-rangeannotation on a separate Ingress rule for/metrics, or use a PrometheusServiceMonitorthat scrapes the pod IP directly (bypassing the Ingress entirely).