TLS / reverse proxy — Caddy
Caddy fronts the Krypton public surfaces with automatic Let's Encrypt TLS — the explorer UI/API at kryscan.com and the public wallet RPC at rpc.kryscan.com. Caddy obtains and renews certs automatically as long as ports 80 + 443 are reachable and the DNS names already point at the host.
The deploy artifacts are in l1/from-source/deploy/explorer/ (Caddyfile, caddy-compose.yml, krypton-caddy.service). This page is the operator-facing version of those files.
Deploy artifact — not yet run on real infra
The Caddyfile and compose are config-validated but have not been run against live DNS/ACME in this repo, and the public testnet is not live yet. Pin a specific caddy: release for production (replace the floating caddy:2 tag).
What it fronts
| Name | Upstream | Notes |
|---|---|---|
kryscan.com | Blockscout nginx proxy | UI + /api + socket on one port |
rpc.kryscan.com | public wallet RPC node | CORS, POST-only edge filtering |
Two different RPC nodes
rpc.kryscan.com must point at the public wallet RPC node — eth,net,web3 only, reth request caps, rate-limited. Do not point it at a validator or at the explorer's archive/debug/trace indexer node. See Block explorer and RPC / full node.
Prerequisites
- DNS:
kryscan.comandrpc.kryscan.comalready resolve to this host. - Ports: 80 (ACME HTTP-01 + HTTP→HTTPS redirect) and 443 (HTTPS, plus 443/udp for HTTP/3) open to the internet. See Ports & firewall.
- Upstreams reachable by name — the Blockscout proxy and the public RPC node, either on a shared docker network (
krypton-web) or via realhost:port.
The Caddyfile
{
email ops@kryscan.com # ACME account (cert-expiry notices) — replace.
# While testing, use the LE STAGING CA to avoid rate limits:
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
# --- Explorer UI + API (Blockscout) ---
kryscan.com {
encode zstd gzip
reverse_proxy blockscout-proxy:80
}
# --- Public JSON-RPC for wallets / dapps ---
rpc.kryscan.com {
encode gzip
# CORS so browser wallets (MetaMask) + dapps can POST from any origin.
@cors_preflight method OPTIONS
handle @cors_preflight {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "POST, OPTIONS"
header Access-Control-Allow-Headers "Content-Type"
respond 204
}
header Access-Control-Allow-Origin "*"
# JSON-RPC is POST-only to "/". Reject other methods cheaply at the edge.
@not_post not method POST OPTIONS
respond @not_post 405
reverse_proxy krypton-public-rpc:8545 {
header_up X-Forwarded-For {remote_host}
}
}An optional ws.kryscan.com block (commented in the artifact) proxies WebSocket eth_subscribe if the public RPC node serves WS — Caddy upgrades WS automatically.
Use the staging ACME CA while testing
Uncomment acme_ca https://acme-staging-v02.api.letsencrypt.org/directory so failed attempts don't burn your Let's Encrypt production rate limits. Switch back for the real cert.
Run it
Quick foreground run for a smoke test:
# DNS for kryscan.com AND rpc.kryscan.com must point here; open :80 + :443 first.
caddy run --config ./CaddyfileAs a managed systemd service (recommended)
caddy-compose.yml runs a tiny Caddy container that persists certs in a caddy_data volume; krypton-caddy.service manages it alongside krypton-node.service.
sudo mkdir -p /opt/krypton/explorer
sudo cp Caddyfile caddy-compose.yml /opt/krypton/explorer/
docker network create krypton-web 2>/dev/null || true # shared net; attach the
# Blockscout proxy + public RPC node
sudo cp krypton-caddy.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now krypton-caddy
# Zero-downtime Caddyfile reload after edits:
sudo systemctl reload krypton-caddyThe unit is Type=oneshot + RemainAfterExit=yes: ExecStart does docker compose up -d, and ExecReload runs caddy reload inside the container for a hot config swap.
Logs:
journalctl -u krypton-caddy -f
docker logs krypton-caddyNetworking the upstreams
The Caddyfile targets blockscout-proxy and krypton-public-rpc by name. Either (a) attach those services to the external krypton-web network and keep the service-name targets, or (b) for different hosts, edit the Caddyfile targets to real host:port (or host.docker.internal) and drop the networks: block.
Back up the cert volume
Back up caddy_data
The caddy_data volume holds the Let's Encrypt certs and private keys. Losing it forces re-issuance, which is subject to ACME rate limits — you can lock yourself out of new certs for hours. Back this volume up.
Rate limiting — honest caveat
Core Caddy has no rate limiter
A public RPC endpoint must be throttled. Core Caddy does not include a limiter, so either:
- add the
caddy-ratelimitplugin and arate_limitdirective, or - put a rate-limiting LB/CDN (Cloudflare, an nginx
limit_req) in front.
Do not run rpc.kryscan.com unthrottled.
Security & caveats
- Pin the Caddy image by tag/digest for production; the artifact uses
caddy:2. - Bind monitoring/admin UIs off the public internet. If you front Grafana/Prometheus with Caddy, add auth — see Monitoring.
rpc.kryscan.comis POST-only + CORS-open by design — that is fine for a public JSON-RPC endpoint, but the upstream node must still expose onlyeth,net,web3with request caps. See RPC / full node.
See also
- Block explorer (kryscan.com) — the Blockscout overlay behind kryscan.com
- RPC / full node (L5) — the public wallet RPC node
- Ports & firewall · Networks & chain IDs