Caddy Routing
All Specter relayer services are exposed through a single Caddy reverse proxy at relayer.specterchain.com. Caddy handles HTTPS termination, automatic certificate renewal, and path-based routing to internal service ports.
Why Caddy
Caddy was chosen over alternatives (Nginx, Traefik, HAProxy) for several reasons:
- Automatic HTTPS: Caddy obtains and renews TLS certificates from Let's Encrypt without any manual configuration or cron jobs. Certificate management is fully automated.
- Simple configuration: The Caddyfile format is concise and readable compared to Nginx's directive-based config.
- HTTP/2 and HTTP/3: Enabled by default with no additional configuration.
- Graceful reloads: Configuration changes are applied without dropping active connections.
Routing Table
All services are accessible through relayer.specterchain.com with path-based routing. Each path prefix is reverse-proxied to the corresponding internal service port:
| Path | Internal Target | Service |
|---|---|---|
/root-updater/* | localhost:3001 | ghost-root-updater |
/commitment/* | localhost:3002 | ghost-commitment-relayer |
/prove/* | localhost:3003 | ghost-proof-relayer |
/faucet/* | localhost:3005 | ghost-faucet |
/registry/* | localhost:3009 | ghost-token-registry |
/ember/* | localhost:3010 | ghost-ember-proxy |
Services without HTTP ports (offline relayer, base conversion relayer, bridge relayers) are not exposed through Caddy. They operate autonomously, watching chain events and submitting transactions without accepting inbound HTTP requests.
Caddyfile Structure
The Caddyfile follows this general structure:
relayer.specterchain.com {
# Root updater
handle /root-updater/* {
reverse_proxy localhost:3001
}
# Commitment relayer (Poseidon computation)
handle /commitment/* {
reverse_proxy localhost:3002
}
# Proof relayer (ZK proof generation)
handle /prove/* {
reverse_proxy localhost:3003
}
# Testnet faucet
handle /faucet/* {
reverse_proxy localhost:3005
}
# Token registry
handle /registry/* {
reverse_proxy localhost:3009
}
# Ember proxy (phantom key access + AI chat)
handle /ember/* {
reverse_proxy localhost:3010
}
# Static icon files for token registry
handle /icons/* {
root * /var/www/specter/icons
file_server
}
}
The handle directive matches path prefixes and routes to the appropriate backend. Caddy strips or preserves the path prefix depending on the configuration — individual services may expect the prefix to be present or absent.
TLS Configuration
Caddy automatically provisions TLS certificates for relayer.specterchain.com via the ACME protocol with Let's Encrypt:
- Certificate type: RSA 2048 or ECDSA P-256 (Caddy's default selection).
- Renewal: Certificates are renewed automatically when they approach expiration (typically 30 days before expiry). No manual intervention required.
- OCSP stapling: Enabled by default. Caddy fetches and staples OCSP responses to improve client TLS handshake performance.
- TLS versions: TLS 1.2 and 1.3 are supported. TLS 1.0 and 1.1 are disabled by default.
DNS Requirements
For automatic certificate provisioning, the following DNS records must be configured:
| Record | Type | Value |
|---|---|---|
relayer.specterchain.com | A | IP address of the relayer droplet |
Caddy uses HTTP-01 ACME challenges by default, which require the domain to resolve to the server and port 80 to be accessible for the challenge response.
CORS Headers
Caddy injects CORS headers for cross-origin requests from the Specter webapp. The CORS configuration is applied globally to all reverse-proxied routes:
header {
Access-Control-Allow-Origin "https://app.specterchain.com"
Access-Control-Allow-Methods "GET, POST, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
Access-Control-Max-Age "86400"
}
Preflight OPTIONS requests are handled by Caddy directly, returning the CORS headers without forwarding to the backend service. This reduces latency for preflight checks and ensures consistent CORS behavior across all services.
For development, localhost origins may be added to the allowed list.
Request Logging
Caddy logs all requests in structured JSON format for debugging and audit purposes:
{
"level": "info",
"ts": 1710600000.123,
"msg": "handled request",
"request": {
"remote_ip": "203.0.113.42",
"method": "POST",
"uri": "/prove/",
"protocol": "HTTP/2.0",
"host": "relayer.specterchain.com"
},
"duration": 13.542,
"status": 200,
"size": 1234
}
Logs are written to /var/log/caddy/access.log and rotated by Caddy or an external log rotation tool to prevent disk exhaustion.
Health Check Routing
Each service's health endpoint is accessible through the Caddy routing:
| Health Endpoint | URL |
|---|---|
| Root updater | https://relayer.specterchain.com/root-updater/health |
| Commitment relayer | https://relayer.specterchain.com/commitment/health |
| Proof relayer | https://relayer.specterchain.com/prove/health |
| Faucet | https://relayer.specterchain.com/faucet/health |
| Token registry | https://relayer.specterchain.com/registry/health |
| Ember proxy | https://relayer.specterchain.com/ember/health |
External monitoring systems can poll these endpoints to detect service outages.
Operational Notes
Reloading Configuration
After editing the Caddyfile, reload Caddy without downtime:
caddy reload --config /etc/caddy/Caddyfile
Active connections are preserved during the reload. New connections use the updated configuration.
Viewing Active Configuration
To inspect the currently running configuration:
caddy adapt --config /etc/caddy/Caddyfile
This outputs the JSON representation of the Caddyfile, useful for debugging routing issues.
Common Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| 502 Bad Gateway | Backend service is down | Check pm2 status and restart the service |
| 503 Service Unavailable | Backend not responding within timeout | Check service logs; may be overloaded |
| Certificate error | DNS not pointing to server | Verify A record for relayer.specterchain.com |
| CORS error in browser | Origin not in allowed list | Add the requesting origin to the CORS header config |
| 404 Not Found | Path does not match any handle block | Verify the path prefix matches the Caddyfile routing |