Reverse Proxy (nginx / Caddy)
The daemon serves its HTTP API + UI on status_listen (default :18334) as
plain HTTP. For a public or friends pool you usually want it behind a reverse
proxy that terminates TLS (HTTPS) on port 443, so miners and the dashboard
reach it over an encrypted, named endpoint (https://pool.example.com).
You have two ways to get HTTPS — pick one:
- Built-in TLS — set
status_tls_listen(and provide a cert) and the daemon serves HTTPS directly. Simplest; no extra software. See Configuration Reference. - Reverse proxy (this page) — an established web front (nginx / Caddy) does TLS, HTTP/2, automatic certificate renewal, access logs and its own rate-limit, forwarding to the daemon over loopback. This is the recommended setup once the pool is internet-facing.
Stratum is not HTTP. Only the API/UI (
:18334) goes through an HTTP reverse proxy. The Stratum endpoints — plain:3333, TLS:3334, and Stratum V2 on its ownsv2_listenport (the reference deployment uses:3336, NOISE NX handshake) — are raw TCP protocols. Expose those ports directly (or through an L4/TCP load balancer such as nginxstream {}or HAProxy TCP mode), never through an HTTPserver {}/reverse_proxyblock.
Two settings that matter behind a proxy
When the daemon runs behind a proxy, set both of these in config.toml under
[server]:
[server]
# The proxy now sees the real client; the daemon only sees the proxy's loopback
# address. Setting this true makes the daemon trust the proxy's X-Forwarded-For
# header for the real client IP — used by the audit log and the per-IP rate-limit
# and ban list. See the warning below.
trust_proxy_headers = true
# The login cookie is only sent over HTTPS. Set true whenever the UI is reached
# via https:// (i.e. always, behind a TLS-terminating proxy).
cookie_secure = true
⚠️ Only set
trust_proxy_headers = truebehind a proxy you control. If the daemon is reachable directly from the internet with this on, any client can forgeX-Forwarded-Forand spoof its IP into your audit log and past your IP bans. Bindstatus_listento loopback (127.0.0.1:18334) so only the proxy can reach it. See Security.
Public wallet pages (/users/<address>)
A per-worker 0% pool exposes a login-free page per miner
at /users/<address>, with ckpool-compatible JSON at /api/users/<address> for
firmware and ckstats. Both are ordinary HTTP on the same status port as the
dashboard, so the catch-all location / (nginx) / reverse_proxy (Caddy) below
already forwards them — no per-route config needed (unlike /api/events,
they are plain request/response, no buffering tweaks). Just confirm they answer
through the proxy: curl https://pool.example.com/api/users/<some-address>
should return the ckpool-style JSON, and https://pool.example.com/users/<address>
should load the wallet page.
Server-Sent Events (/api/events)
The dashboard’s live updates (block-found, new-job, …) use a long-lived
Server-Sent-Events stream at GET /api/events. A proxy that buffers responses
or times the connection out will break live push. Both examples below disable
response buffering and raise the read timeout for that path.
Caddy
Caddy gets you automatic HTTPS (Let’s Encrypt) with almost no config:
pool.example.com {
reverse_proxy 127.0.0.1:18334 {
# SSE: stream the response, don't buffer it.
flush_interval -1
}
}
That’s the whole file. Caddy fetches and renews the certificate, terminates TLS,
forwards X-Forwarded-For / X-Forwarded-Proto automatically, and flush_interval -1 keeps /api/events flowing.
nginx
server {
listen 443 ssl http2;
server_name pool.example.com;
ssl_certificate /etc/letsencrypt/live/pool.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pool.example.com/privkey.pem;
# Dashboard, API, and the public wallet pages (/users/<address> +
# ckpool-compatible /api/users/<address> JSON) — all plain HTTP, no
# per-route tuning needed.
location / {
proxy_pass http://127.0.0.1:18334;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Server-Sent-Events stream: no buffering, long-lived connection.
location /api/events {
proxy_pass http://127.0.0.1:18334;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h;
}
}
# Redirect plain HTTP → HTTPS
server {
listen 80;
server_name pool.example.com;
return 301 https://$host$request_uri;
}
Use certbot (certbot --nginx -d pool.example.com) for the certificate.
Checklist
-
status_listen = "127.0.0.1:18334"— daemon only reachable via the proxy. -
trust_proxy_headers = trueandcookie_secure = truein[server]. -
/api/eventsis proxied without buffering (Caddyflush_interval -1/ nginxproxy_buffering off). - Public wallet pages reachable through the proxy:
/users/<address>loads and/api/users/<address>returns JSON (per-worker pools only; served by the catch-all block, no special config). - Stratum ports (
:3333plain,:3334TLS,sv2_listene.g.:3336) exposed directly, not via the HTTP proxy. - After changing
[server], restart the daemon.