Configuration Reference
Server and client read TOML configs from /etc/hash-relay/server.toml and /etc/hash-relay/client.toml. The install scripts generate these from the annotated templates shipped with each release:
The snippets below mirror the current templates (v0.2.2). Placeholders like {{JWT_SECRET}} are substituted by the installer.
server.toml
Logging — [log]
[log]
file_enabled = true
log_dir = "/var/log/hash-relay"
log_name = "hr-server"
rotation = "daily" # daily | hourly | never
max_log_files = 30
level = "info" # trace | debug | info | warn | error | off
# timezone = "+8" # optional; recommended on OpenWrt without tzdataRelay listener — [server]
[server]
listen = "0.0.0.0"
tcp_port = 8555
quic_port = 9555Encryption / TLS — [server.enc]
[server.enc]
# TLS cert source (shared by TCP and QUIC):
# acme — ACME auto issue/renew (production)
# file — manual cert files
# self_signed — persistent self-signed cert (LAN / no domain)
tls = "self_signed"
frame_encrypt_alg = 0 # 0=off 1=AES-GCM 2=ChaCha20
# --- tls = "acme" ---
# domain = "relay.example.com"
# contact = "mailto:admin@example.com"
# cache_dir = "/var/lib/hash-relay/certs"
# acme_challenge_port = 8080 # when port 80 is taken by Nginx/Caddy
# --- tls = "file" ---
# cert_path = "/etc/ssl/certs/hash-relay/fullchain.pem"
# key_path = "/etc/ssl/private/hash-relay/privkey.pem"
# Cert hot-reload: server checks mtime every 12h; no restart after renewal.
# --- tls = "self_signed" ---
cache_dir = "/var/lib/hash-relay/certs"
# self_signed_san = "203.0.113.10"
# On first start, server logs pin_sha256 — set on clients for cert pinningAuthentication — [server.auth]
[server.auth]
# type: "token" (static token) | "challenge" (challenge-response)
type = "token"
max_timestamp_drift = 300Client access tokens are managed in the client_nodes table via the admin UI or API.
Database — [server.database]
[server.database]
# Driver is selected from URL scheme (MySQL / PostgreSQL / SQLite)
url = "sqlite:///var/lib/hash-relay/hashrelay.db?mode=rwc"
# url = "mysql://user:password@127.0.0.1:3306/hashrelay"
# url = "postgres://user:password@127.0.0.1:5432/hashrelay"
max_connections = 50
min_connections = 5
worker_event_retention_days = 90 # 0 = disable purge
worker_extra_retention_days = 90Management API — [server.api]
[server.api]
listen = "0.0.0.0" # use 127.0.0.1 + reverse proxy when possible
port = 8080
tls = false # true = HTTPS on API port (same cert as [server.enc])
jwt_secret = "CHANGE_ME_TO_A_LONG_RANDOM_STRING"
jwt_expiry = 86400 # seconds (24 h)
compress = true # zstd preferred, then gzipWeb admin UI — [server.web]
[server.web]
enabled = true
# port = 8081 # omit to share [server.api].port
dir = "/var/lib/hash-relay/web"
compress = true
# /api/v1/* → API; other paths → static frontendConnections — [server.connection]
[server.connection]
heartbeat_interval = 60
heartbeat_timeout_count = 3
max_client_connections = 0 # 0 = unlimitedProtocol mirroring & alerts — [sniffing]
Per-pool sniffing is toggled via pools.sniffing_enabled in the database. These are global engine settings:
[sniffing]
channel_capacity = 8192
parser_workers = 0 # 0 = auto (CPU cores / 4, min 1)
flush_interval = 30 # miner stats DB flush interval (seconds)
[sniffing.alerts]
enabled = true
reject_rate_threshold = 0.10 # 10%
reject_rate_window = 50 # last N shares
offline_threshold = 300 # seconds
hashrate_drop_threshold = 0.50 # vs 15-min average
hashrate_zero_periods = 3
consecutive_stale_threshold = 5
high_latency_threshold_ms = 5000
reconnect_window = 300
reconnect_threshold = 3
alert_cooldown = 600
[sniffing.alerts.webhook]
enabled = false
url = ""
timeout = 10
# [sniffing.alerts.webhook.headers]
# Authorization = "Bearer xxx"Miner admin proxy — [server.admin_proxy]
Lets operators open a miner's built-in web UI from the dashboard while the miner stays on a private LAN. Flow: Browser → server /proxy/{token}/… → client → miner.
[server.admin_proxy]
enabled = true
open_timeout_secs = 10
ready_timeout_secs = 120
idle_timeout_secs = 600
max_lifetime_secs = 7200
token_ttl_secs = 1800
drain_timeout_secs = 30
max_per_admin = 3
max_per_node = 10
max_global = 100
max_per_worker = 2
max_concurrent_http = 6License (optional) — [license]
# [license]
# instance_id = "my-server-01" # or HASHRELAY_INSTANCE_ID
# key = "LIC-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
# token_cache = "/var/lib/hash-relay/license_token.dat"client.toml
Logging — [log]
[log]
file_enabled = true
log_dir = "/var/log/hash-relay"
log_name = "hr-client"
rotation = "daily"
max_log_files = 30
level = "info"Server endpoint — [client]
[client]
server_addr = "relay.example.com"
tcp_port = 8555 # must match server [server].tcp_port
quic_port = 9555 # must match server [server].quic_port
client_id = "farm-a"
token = "CHANGE_ME_ACCESS_TOKEN"Encryption — [client.enc]
[client.enc]
frame_encrypt_alg = 0 # must match server
insecure = true # skip cert validation (testing / self-signed)
# pin_sha256 = "..." # from server startup logs (recommended)Connection management — [client.connection]
[client.connection]
reconnect_interval = 5
max_reconnect_attempts = 0 # 0 = unlimited
heartbeat_interval = 60
heartbeat_timeout_count = 3protocol field.Hardware telemetry — [client.telemetry]
Periodically polls miner admin APIs (asic-rs) for connected miners and reports stats to the server.
[client.telemetry]
enabled = true
collect_interval_secs = 300Miner admin proxy — [client.admin_proxy]
When the server opens a proxy tunnel, the client dials the miner's web admin port on the LAN and relays HTTP/WebSocket bytes over the control channel.
[client.admin_proxy]
enabled = true
max_sessions = 10
idle_timeout_secs = 600Environment Variable Overrides
Install scripts accept these overrides. At runtime, DATABASE_URL and RUST_LOG can also be set in the systemd/procd unit:
| Variable | Applies To | Effect |
|---|---|---|
| LISTEN | server install | Relay bind address ([server].listen) |
| TCP_PORT / QUIC_PORT / API_PORT | server install | Relay and API ports |
| API_LISTEN | server install | API/Web bind address ([server.api].listen) |
| DATABASE_URL | server | Override [server.database].url at runtime |
| JWT_SECRET | server install | Set [server.api].jwt_secret |
| DATA_DIR / BIN_DIR / WEB_DIR / LOG_DIR | server install | Override install paths |
| SERVER_ADDR | client install | Relay hostname or IP |
| TCP_PORT / QUIC_PORT | client install | Must match server ports |
| CLIENT_ID / TOKEN | client install | Node identity and access token |
| DATA_DIR / BIN_DIR | client install | Override install paths |
| RUST_LOG | both | Override [log].level |
| CONFIG_PATH | server | Config file path (set by service unit) |
TLS Modes
- self_signed (default) — persistent self-signed cert for LAN or no domain; copy
pin_sha256from server logs to clients - acme— automatic Let's Encrypt issuance and renewal; set
domain,contact, andacme_challenge_portwhen port 80 is occupied - file — supply
cert_path/key_path; hot-reloads every 12 hours without restart
[server.api].tls = false when Nginx or Caddy terminates TLS in front of the management port. Enable it only when the API/Web UI should speak HTTPS directly.