Skip to content

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

NameUpstreamNotes
kryscan.comBlockscout nginx proxyUI + /api + socket on one port
rpc.kryscan.compublic wallet RPC nodeCORS, 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.com and rpc.kryscan.com already 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 real host:port.

The Caddyfile

text
{
	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:

bash
# DNS for kryscan.com AND rpc.kryscan.com must point here; open :80 + :443 first.
caddy run --config ./Caddyfile

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.

bash
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-caddy

The 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:

bash
journalctl -u krypton-caddy -f
docker logs krypton-caddy

Networking 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-ratelimit plugin and a rate_limit directive, 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.com is POST-only + CORS-open by design — that is fine for a public JSON-RPC endpoint, but the upstream node must still expose only eth,net,web3 with request caps. See RPC / full node.

See also

Operator docs. Testnet chain-id 473374; mainnet 47337 (gated on external audit). Not financial advice.