It sits between miners and the upstream pool, keeps miner-facing difficulty local, fans out fresh jobs, balances miners across configured pools, and exposes a lightweight HTTP monitor for MoneroOcean-style XMR-family deployments.
- Modern Node.js runtime with
node >= 18 - Local miner-facing difficulty control and job fanout
- Balancing and failover across configured upstream pools
- Built-in HTTP monitor with optional basic auth
- Structured logs with low operational noise
- Miner side: XMR-style JSON-RPC methods such as
login,getjob,submit, andkeepalive - Pool side: XMR-style jobs that expose proxy-compatible nonce offsets
- Good fit: Monero-style forks and MoneroOcean's XMR-family algo-switching path
Not a fit:
- Ethash / Etchash
- Kawpow
- Autolykos2
- MoneroOcean
XTM-C/c29pool protocol
Those use different stratum families than this proxy's XMR-style path.
This proxy is intended to sit in front of a controlled miner fleet, not to act as a zero-trust public pool edge.
- Normal miner-difficulty shares intentionally trust the miner-reported
resultso the proxy can keep per-share CPU cost low. - The proxy still enforces job, template, and duplicate-share checks locally and can verify pool-target candidates, but it is not a hostile-miner firewall.
- If you need full cryptographic verification of every submitted share from untrusted miners, enforce that in a different layer.
- Install Node.js
18+and build tools required for native hashing modules. - Clone the repo and install dependencies:
npm install- Copy the sample config and edit it for your wallet, pool, and ports:
cp config_example.json config.jsonThe bundled config_example.json uses YOUR_WALLET placeholders on purpose. Replace those with your own wallet before you start the proxy.
- Run the test suite before first deployment:
npm test- Start the proxy:
node proxy.jsUseful flags:
node proxy.js --config /path/to/config.jsonnode proxy.js --workers 4
Config reload:
- Send
SIGHUPto the running proxy to reloadconfig.jsonin place - In
pm2, usepm2 sendSignal SIGHUP xnp
Three reasonable ways to run the proxy are supported here.
Manual local install:
- best if you want the clearest view of what is installed and how the proxy runs
- easiest path for development, debugging, and local edits
- downside: you install system packages and Node dependencies yourself
install.sh:
- best if you want a quick local setup on a supported Linux host without typing each install step yourself
- it sets up packages, local npm dependencies, default config, and self-signed certs
- downside: it still installs software directly onto the host machine and depends on distro package availability for Node.js
18+
Docker:
- best if you want isolation from the host and a repeatable container image
- keeps the runtime inside the container instead of mixing files into the host system
- downside: you still need to understand volume mounts for
config.jsonand optional cert files, so it is not always the simplest first path for a beginner
For most beginners:
- use manual local install if you want to learn the moving parts
- use
install.shif you are on Ubuntu and want the fastest host install - use Docker if you already know basic container workflows or want cleaner isolation
For a supported Linux local install from a repo checkout:
bash install.shWhat it does:
- installs the required packages with
apt,dnf, oryum, depending on the host - runs local
npm install - creates
config.jsonfromconfig_example.jsonif needed - generates
cert.keyandcert.pemonly when both are missing - verifies the install with
npm test
Requirements:
- run it from an
xmr-node-proxycheckout - a Linux host with
apt,dnf, oryum - Ubuntu 26.04, Rocky/Alma/RHEL 9+, or another compatible distro with equivalent package names
- either
rootor a normal user withsudo
Safety notes:
- if apt installs a Node.js older than
18, the script stops with an explicit error - if only one of
cert.keyorcert.pemexists, the script stops instead of overwriting the surviving file
After it completes:
node proxy.js --config /path/to/xmr-node-proxy/config.jsonFor updating an existing checkout in place:
bash update.shUse it only when you want this checkout force-synced to the latest origin/master state.
Important:
- local repo changes are discarded
- untracked files in the repo are removed
- ignored files such as
config.json, TLS certs, andnode_modules/are left in place
Build the image from the repo root:
docker build -t xmr-node-proxy .Run it with a local config mounted into the container workdir:
- this example assumes your mounted
config.jsonlistens on3333and enables the HTTP monitor on8081 - adjust
-pmappings to match your actuallisteningPorts[]andhttpPort - the bundled
config_example.jsondefaults tohttpEnable: false, so turn that on first if you want the monitor on8081
docker run --rm -it -p 3333:3333 -p 8081:8081 -v "$PWD/config.json:/xmr-node-proxy/config.json:ro" xmr-node-proxyIf your config uses TLS listener files from the repo root, mount those too:
docker run --rm -it -p 3333:3333 -p 8443:8443 -p 8081:8081 -v "$PWD/config.json:/xmr-node-proxy/config.json:ro" -v "$PWD/cert.key:/xmr-node-proxy/cert.key:ro" -v "$PWD/cert.pem:/xmr-node-proxy/cert.pem:ro" xmr-node-proxyFor a single MoneroOcean upstream over TLS:
{
"pools": [
{
"hostname": "gulf.moneroocean.stream",
"port": 20001,
"ssl": true,
"allowSelfSignedSSL": true,
"share": 100,
"username": "YOUR_WALLET",
"password": "proxy",
"keepAlive": true,
"algo": ["rx/0"],
"algo_perf": { "rx/0": 1 },
"algo-min-time": 60,
"blob_type": "cryptonote",
"default": true
}
],
"listeningPorts": [
{
"port": 3333,
"ssl": false,
"diff": 1000
}
],
"bindAddress": "0.0.0.0",
"httpEnable": true,
"httpAddress": "127.0.0.1",
"httpPort": 8081,
"difficultySettings": {
"minDiff": 1,
"maxDiff": 10000000,
"shareTargetTime": 30
}
}MoneroOcean note:
- TLS port
20001currently requiresssl: trueandallowSelfSignedSSL: true - If your miner provides a real MoneroOcean
algo-perfmap, pass it through so pool-side algo selection stays accurate algo-min-timeis optional;0still maps to the upstream pool's default stickiness window onnodejs-pool, which is effectively60
Default node proxy.js mode uses all CPU cores available on the host.
To cap worker count explicitly:
node proxy.js --workers 2Recommended production launch:
pm2 start proxy.js --name xnp -- --config /home/nodeproxy/xmr-node-proxy/config.json
pm2 savePM2 best practice:
install.shdoes not install PM2 for you.- Prefer installing PM2 from your distro package manager or from a user-owned Node toolchain instead of using a system-wide
npm install -g. - Do not add
--log-date-formathere. The proxy already timestamps every log line, so PM2-side timestamps only duplicate output. pm2 logs xnpandpm2 monitwork well with the new structured log format.pm2-logrotateis still a good companion for long-running nodes.pm2 sendSignal SIGHUP xnpreloads config without replacing the main process
Logs now use one consistent format:
2026-04-19 08:13:08 INF master pool.job host=gulf.moneroocean.stream height=3387651 algo=rx/0 target=5000
Format:
timestamp level component event key=value...
Operational notes:
- Summary lines are throttled so they only print on meaningful change or once per minute
- Warnings and errors include the fields you usually need first:
host,port,miner,job,nonce,reason,error - If you are running behind another logger that already stamps lines, avoid adding a second timestamp layer
- Set
XNP_LOG_TIME=0if you want the proxy to emitlevel component event ...without its own timestamp prefix
Important fields:
pools[]: upstream poolspools[].share: target balancing weight among active non-dev pools;0means backup-onlypools[].default: choose the default pool; if older configs mark more than one, the last one winspools[].algo,pools[].algo_perf, and optionalpools[].algo-min-time: upstream algo declaration for pools such as MoneroOceanlisteningPorts[]: miner-facing ports and their starting difficultydifficultySettings: local vardiff bounds shared across miner-facing portsaccessControl: optional wallet/password allowlist that reloads from diskhttpEnable,httpAddress,httpPort,httpUser,httpPass: built-in monitor and optional basic authtls.keyPath,tls.certPath: local certificate pair for SSL listening portssocketTimeoutMs,maxJsonLineBytes: defensive limits for bad or stuck peers
Validation rules enforced at startup:
- At least one pool must exist
- At least one listening port must exist
- At least one default non-dev pool must exist
difficultySettings.minDiff,difficultySettings.maxDiff, anddifficultySettings.shareTargetTimemust all be positive, andminDiff <= maxDiff
Notes:
daemonAddressis not used by the current runtime. Remove it from older configs.- Old
coinSettingsconfigs are rejected at startup. Rename that block todifficultySettings. - The sample config intentionally uses placeholder wallets. The built-in developer-share path is separate and only applies if
developerShare > 0.
Set httpEnable: true to expose the built-in monitor.
What it shows:
- connected miners
- hashrate and pool distribution
- active and fallback pools
- recent miner activity
If httpUser and httpPass are both set, the monitor requires HTTP basic auth.
Recent live tests against MoneroOcean TLS upstream succeeded for:
rx/0rx/arqpantheraghostridercn/gpu
Important limits:
- The proxy only handles the XMR-style JSON-RPC path
- SupportXMR-style single-coin XMR pools continue to use that same path
- Active MoneroOcean algos that use non-XMR stratum families are out of scope here
XTM-C/c29is a separate pool-side protocol and should not be treated as a drop-in extension of the XMR path
The repo includes a local test suite. It does not need a live pool.
npm testNo active block template
- The upstream pool is connected but has not produced a usable job yet.
Unauthorized access
- Check
accessControl.enabledand the contents of the configured control file.
Duplicate timestamps in logs
- Remove PM2's
--log-date-formator any equivalent external timestamp prefix.
TLS upstream fails on MoneroOcean 20001
- Ensure
ssl: trueandallowSelfSignedSSL: trueare both set.
Miners submit low-diff shares constantly
- Use a better matching listening port difficulty or set fixed difficulty on the miner if needed.
If you want to support the project directly, optional XMR donations can be sent to:
89TxfrUmqJJcb1V124WsUzA78Xa3UYHt7Bg8RGMhXVeZYPN8cE5CZEk58Y1m23ZMLHN7wYeJ9da5n5MXharEjrm41hSnWHL
- MoneroOcean for long-running maintenance and ongoing proxy evolution
- Alexander Blair and Snipa22 for the original public codebase and early architecture
- djfinch, M5M400, Learner, Mike Teehan, Ethorsen, and tosokr for follow-up fixes, compatibility work, and docs
- 1rV1N, MinerCircle, Mayday30, Connor, J. Meister, Mi!, Tom, mrmoo85, piratoskratos, slayerulan, sph34r, sunk818, sunxfof, tinyema, BK, and other smaller contributors for fixes and operational improvements