RigForge reads a small config.json in the repo root. It holds only what the script can't infer. The
rest (CPU profile, thread count, HugePage sizing) is detected and applied for you.
On first run, if there's no config.json, setup creates a minimal one interactively (it asks for
your pool URL and optional stratum password). For an .onion host it explains that you must
install and run Tor yourself, defaults socks5 to 127.0.0.1:9050, and offers a validated
interactive override (Enter or EOF keeps the default). LAN pools get no proxy prompt or key.
You can also pre-create one from
config.minimal.json.
Setup validates every field when it parses the config. A malformed pool URL, an out-of-range port, a
bad hostname, a non-boolean flag, or an unsafe HOME_DIR stops setup with a message rather than
producing a config the miner would reject.
The only required field is the pool. RigForge uses XMRig's native pools array, and a pool only needs
its url (a host:port). Everything else falls back to a default:
{
"pools": [
{ "url": "<YOUR_POOL_HOST>:3333" }
]
}That's a complete config. Replace <YOUR_POOL_HOST>:3333 with your pool's host and port (Pithead's
proxy listens on 3333). The interactive first-run setup writes this minimal shape for a LAN pool without a password.
Mining to a public pool like SupportXMR? A
urlalone isn't enough: public pools also need your Monero wallet as the pooluser(and usually a TLS port). See Connecting to a public pool for a copy-paste example.
Two-tier config (like Pithead): keep
config.jsonminimal and add only the keys you want to change.config.reference.jsonlists every key with its default. Copy in what you need; anything you omit keeps the default. The reference table below documents each key.
Key naming: the three legacy keys (ACCESS_TOKEN, DONATION, HOME_DIR) keep their historical
SCREAMING names forever — renaming them would break deployed fleets, and aliasing would double the
documented surface. Every other documented key, and every future one, is lowercase snake_case,
matching Pithead. (RIG_NAME is a reserved SCREAMING name seeded by the appliance image build; it is
not read by parse_config today, so it isn't in the table below.)
| Key | Default | What it does |
|---|---|---|
pools |
(required) | XMRig's native pools array — the pool(s) to mine to. Each entry needs a url (host:port); every other field falls back to a Pithead default. A pool's user is the rig's dashboard label (defaults to the hostname). List multiple entries for failover. See Pools. |
| — | — | Unknown keys warn and are ignored (with a did-you-mean for near-misses) — a typo'd key, especially ACCESS_TOKEN, must never silently not apply. Keys starting with _ are comments. |
api |
"disabled" |
"enabled" serves the sister API: a second read-only port with XMRig's /1/summary+/2/summary passed through verbatim plus a namespaced rigforge object (tune state, RAPL watts, firmware/health probes, pinned versions), and /health + /tune endpoints. One tiny persistent stdlib server; a systemd timer refreshes its data every 15 s, so requests never touch the miner (see operations › sister API). Gated by ACCESS_TOKEN or any named ACCESS_TOKENS entry; Linux-only. control: "enabled" implies this (control is unobservable without the read feed) — but api is not itself in control's writable key set, so a Pithead stack can never turn it on remotely; it only ever comes on from local config. |
api_port |
8081 |
Sister API port (8080 is rejected — that's XMRig's own API). |
api_bind |
"0.0.0.0" |
Sister API listen address. |
control |
"disabled" |
"enabled" serves the writable control path (#236): a separate authenticated port that lets a Pithead stack apply config changes through RigForge, so config.json stays the source of truth (the producer for pithead Worker Inspect). Implies api: "enabled" — control has no read side of its own, so Pithead confirms an applied change through the sister API's enriched feed; no need to also set api by hand. Fail-closed: enabling it requires both ACCESS_TOKEN and api_allow_from — a writable API with no token or no pinned source is refused with a hard error. Only pools, DONATION, autotune, watchdog(+watchdog_interval_min), and max_temp_c are writable through it; anything else is rejected. A change touching only watchdog_interval_min and/or max_temp_c applies without restarting XMRig (#381) — every other key restarts it. The remote path additionally refuses to disable watchdog or to unset / out-of-band max_temp_c — a rig's thermal protection can only be removed by a local rigforge.sh apply on the box (#257). Each change is validated, the old config is snapshotted to config-backups/ first, and a change that doesn't come back live is rolled back. The receiver holds no privilege and stages off the request path, so writes never touch mining. Linux-only. See Operations › Control path. |
control_port |
8082 |
Control path port (rejects 8080 and the api_port). |
control_bind |
"0.0.0.0" |
Control path listen address. Pair with api_allow_from (required) to pin who may write. |
miner_user |
"" (root) |
Run the miner as this dedicated non-root system user (created at setup, nologin). RigForge applies the CPU's MSR preset root-side before start; on families without a known preset the ~10-15% MSR boost is skipped — which is why this ships opt-in. Lowering privilege changes nothing else: HugePages come from the boot reservation, tune/doctor/apply all keep working. |
api_allow_from |
"" (off) |
Restrict the API port(s) — :8080, :8081 when the sister API is on, :8082 when the control path is on — to a single IPv4 or IPv6 address/CIDR (plus loopback), via an own nftables table (renders ip saddr or ip6 saddr per family). SSH and mining are never touched. For IPv6 to be reachable-and-scoped, set api_bind/control_bind to :: (the servers then bind dual-stack). Needs nft; Linux-only. |
ACCESS_TOKEN |
"" (open) |
The master bearer token, and the only one XMRig itself carries. Unset leaves the read APIs open. When set, it is the token rendered into XMRig's own :8080 API, the one RigForge's own liveness/rollback probe uses when the control path restores pools after a failed apply, and an accepted bearer on :8081/:8082 like any ACCESS_TOKENS entry. Read-only :8081 also accepts its scoped HMAC-derived read bearer for Pithead 2.0 when the token is a randomly generated 32+ character value (openssl rand -hex 16). Short legacy tokens remain raw-client compatible but get no derived bearer. A stack that probes :8080 directly must hold this one — a named token cannot be honoured there, because XMRig takes exactly one http.access-token. See Pithead Integration. |
ACCESS_TOKENS |
{} (none) |
Optional name → token map: one token per Pithead stack that consumes this rig, so rotating the bench's token never touches production's and a bench borrow never needs prod's credential (#516). Every entry is accepted on the read-only sister API (:8081) and the writable control path (:8082) exactly as ACCESS_TOKEN is — raw match on both, plus that entry's own scoped HMAC-derived read bearer on :8081 under the same 32+ ASCII character rule. Entries grant no access to XMRig's :8080 and are never used for the control path's pools restore; both stay ACCESS_TOKEN's. Names are yours (e.g. {"prod": "…", "bench": "…"}); values take the same characters ACCESS_TOKEN does (letters, digits, . _ - : @ +). Tokens are read once at service start, so revoking an entry means removing it and running sudo rigforge.sh apply. rigforge.sh doctor reports how many are configured and which one XMRig carries. |
DONATION |
1 |
XMRig donate level, an integer 0–100 (percent). Patched into the build (donate.h) and written to the generated config, so it must be a valid integer or setup fails fast. Lowering it below the level the current binary was compiled with needs a rebuild — XMRig clamps the config value to its compiled floor (and autosaves the clamped value), so apply alone cannot lower it. Raising it works with a plain apply. |
HOME_DIR |
DYNAMIC_HOME |
Where worker files live. DYNAMIC_HOME puts them in data/worker inside the repo; set an absolute path to use <path>/worker instead. |
autotune |
"disabled" |
Periodic live tuning, as a target: "disabled" (default) installs no timer; "performance" schedules a periodic tune for raw hashrate; "efficiency" schedules one for hashrate-per-watt (needs a power source, built-in RAPL or TUNE_POWER_CMD, else it falls back to performance with a warning). Legacy booleans still parse (true → performance, false → disabled). This key controls the schedule; to run one live pass by hand, use tune --now (or tune --now --long for a full all-knob sweep). See Operations › Live auto-tuning. |
watchdog |
"disabled" |
"enabled" installs a systemd timer that health-checks the miner every watchdog_interval_min minutes and restarts it when it's wedged — process alive but two consecutive checks see 0 H/s or an unreachable API (the case Restart= can't catch). One bad check never restarts (it could be a restart-in-progress). Legacy booleans parse (true → enabled); a typo hard-errors rather than silently disabling recovery. Linux-only. See Operations › Watchdog. |
watchdog_interval_min |
5 |
Watchdog check cadence in minutes (1–1440). |
max_temp_c |
"" (off) |
Optional thermal cutoff for the watchdog: above this °C the miner is stopped, and started again once the rig cools 5 °C below it (fixed hysteresis). Empty = no thermal logic. The sensor is /sys/class/thermal/thermal_zone0/temp when it exists, else the CPU hwmon (k10temp/coretemp) — and k10temp reports Tctl, a control temperature that runs high by design (a loaded EPYC reads ~90 °C Tctl in normal operation). Check the live reading before choosing a cutoff (THERMAL_ZONE overrides the path). Accepts 40–110. |
hugepages_reserve_extra_mb |
0 |
MB of HugePages to leave for the rest of the box when RigForge sizes its reservation — for co-locating a miner on a busy host (e.g. a Pithead stack, whose 2MB pages come from the same kernel pool). RigForge stays the sole writer of the reservation; this key just declares how much of it isn't the miner's. Added to both the boot reservation and the runtime pool. When the box's existing reservation already covers stack + miner, RigForge changes nothing and needs no reboot. |
hugepages_pool_ceiling_mb |
0 (no ceiling) |
A hard cap, in MB, on the 2MB HugePages pool RigForge will reserve — the opposite direction from hugepages_reserve_extra_mb, which adds to the computed requirement rather than bounding it. Set it on a RAM-constrained co-resident box: the runtime vm.nr_hugepages write is never grown past the ceiling, and neither is the hugepages= RigForge merges into the GRUB cmdline, so the pool does not come back up over the ceiling after a reboot (the grow-only runtime write could never bring it down again). An odd MB value floors to the 2MB page below (5121 → 5120 MB effective) — a cap rounds toward less memory, never more. The 1GB dataset reservation is a separate pool this key does not bound, so a ceiling below a box's 1GB reservation still leaves it holding more hugepage memory than the ceiling names. |
threads |
"" (auto) |
A ceiling on the RandomX thread count — min(auto-detected, threads), never a raise. A co-located miner sets it (e.g. nproc-2) to leave the stack its cores. Both the generated cpu.rx and the HugePages sizing honour it, so the reservation matches the threads actually run. |
add_to_path |
false |
When true, setup installs a rigforge command on your PATH (a symlink in /usr/local/bin) so you can run sudo rigforge <cmd> from any directory. Off by default. uninstall removes it. |
You don't write XMRig's config. RigForge generates it in-script and writes it into the worker root as
the live config.json the service runs from. There's no template file to keep in sync and no config
key for it. Every run (re-runs included) rebuilds the config from four sources:
- Your
config.json: thepoolsarray (withuser/pass/keepalive/tlsand failover defaults filled in), thedonate-level, and thehttpAPI block (bound to the LAN, read-only, open by default; setACCESS_TOKENfor a bearer token). These are the keys documented in the reference table. - Detected hardware: the per-CPU
cpu/randomxtuning (thread count,asm, MSR, NUMA, HugePages). RigForge uses XMRig's own cache-aware auto-detection rather than a CPU-model table, so it stays correct for CPUs it's never seen. See Hardware Requirements. - Static defaults: the fixed knobs every worker shares, emitted directly:
autosave,randomx.mode: fast,randomx.init,opencl/cudaoff, and thehttpport8080. - Tuned overrides (if present): if you've run
tune, its winning knobs intune-overrides.jsonare merged on top as the final step, so tuning wins for just the keys it sets and yourconfig.jsonis never edited.
Because the config is rebuilt from these sources every time, editing the generated config.json by
hand has no effect. Change your repo-root config.json (or tune) and re-run instead.
Don't put a wallet address in the worker
userwhen using Pithead. The stack handles payouts centrally; the pooluseris just a rig label (it defaults to the hostname so you can tell workers apart on the dashboard).
The pool target is XMRig's native pools array, passed straight through to XMRig, so you can use any
field XMRig supports. Only url is required; every other field has a default, so you specify only what
you care about:
| Field | Default if blank/omitted |
|---|---|
url |
(required) — host:port (e.g. pool.supportxmr.com:443 or your-stack:3333). For an IPv6 literal, use the bracketed [2001:db8::1]:3333 form. |
user |
the machine hostname. For Pithead this is the rig's dashboard label; for a public pool set it to your Monero wallet address (see below). |
pass |
"x" — the stratum password / worker name. For an open Pithead stack the default works; if the operator enabled the stack's p2pool.stratum_password, set this to that secret or the proxy rejects the rig. See Pithead Integration › Stratum authentication. |
keepalive |
true |
tls |
false — set true when you connect on the pool's TLS/SSL port. |
tls-fingerprint |
null (no pin) — the pool cert's SHA-256 as 64 hex chars. XMRig does no CA validation on stratum TLS, so the pin is the only server authentication; without it, TLS encrypts but doesn't authenticate. Requires "tls": true. See Pithead Integration › Stratum over TLS. |
socks5 |
null (direct connection) — dial this pool through a SOCKS5 proxy at host:port, e.g. "127.0.0.1:9050" for a local Tor client. Same address rules as url. XMRig sends the pool's hostname to the proxy rather than resolving it first, so a v3 .onion stratum works with no extra setting. RigForge points the miner at a proxy; running one is yours to arrange. |
enabled |
true |
Two common setups follow; pick the one that matches where you're mining.
Pithead handles pool selection, payouts, and the
P2Pool/XvB split centrally, so the worker only needs the stack host and its proxy port (3333). The
user is just a label for the dashboard, so don't put a wallet address here:
{
"pools": [
{ "url": "stack.lan:3333", "user": "garage-rig" }
]
}user is optional (it defaults to the hostname); set it to tell workers apart on the dashboard. See
Pithead Integration for discovery and the API token.
A public pool pays you, so it needs your Monero wallet address as the login (user) and almost always a
TLS port. RigForge builds stock upstream XMRig, so it speaks standard Stratum to any RandomX pool. Fill
in the pool's endpoint and your wallet:
{
"pools": [
{
"url": "pool.supportxmr.com:443",
"user": "YOUR_MONERO_WALLET_ADDRESS",
"pass": "garage-rig",
"tls": true
}
]
}useris your Monero wallet address. This is who gets paid. Many pools also acceptWALLET.workernamehere to label the rig in their dashboard.passis a worker name (or just"x"; most public pools ignore the password).url+tlsis the pool's stratum endpoint. Use the pool's TLS/SSL port (often:443or:5555) with"tls": true; a plain, unencrypted port needs notls. For a self-signed or internal cert, addtls-fingerprintto pin it. Your pool's Getting started / Connect page lists its exact host, ports, and whether it wantswalletorwallet.worker.
Save that as config.json, then sudo ./rigforge.sh apply (a fresh setup picks it up too).
The pool host must be an IP or DNS-resolvable hostname; allow its Stratum port through any firewall.
List multiple entries. XMRig tries them in order and fails over to the next if one is unreachable, handy for a primary stack with a public-pool fallback:
{
"pools": [
{ "url": "stack.lan:3333" },
{ "url": "pool.supportxmr.com:443", "tls": true }
]
}Here the worker mines to stack.lan:3333 and falls back to pool.supportxmr.com:443 over TLS, with
user/pass/keepalive filled in for both.
Edit config.json, then apply it in one step:
sudo ./rigforge.sh applyapply re-reads config.json, regenerates the live XMRig config, and restarts the service, with no
recompile. It's the path for a pools change, a new rig label, TLS, failover pools, and the like. (On
macOS — deprecated, unsupported and untested since 2026-09-14 — there's no service, so apply
regenerates the config and you restart the miner yourself; see
Operations › Running on macOS.)
You can also re-run full setup (sudo ./rigforge.sh), but that re-provisions the whole worker
(dependencies, build, kernel tuning, service). To avoid interrupting a running miner, a setup re-run on
an already-built worker regenerates the config without restarting, so the new config only takes effect
on the next restart. To apply an edit, use apply; it does the restart for you. Both are idempotent and
skip the recompile when the pinned XMRig is already built.
NOTE:
DONATIONis also compiled into the XMRig binary at build time, so on an already-built worker neitherapplynor a setup re-run changes it; both update only the runtime config. To re-patch the binary, force a rebuild: remove<WORKER_ROOT>/xmrig(or bump the pinned XMRig) and run setup, or runupgradeafter bumping the pin.
- Getting Started — first-run setup.
- Hardware Requirements — the auto-detected tuning that drives the generated
cpusection. - Pithead Integration — the API token and discovery rules.