Configuring the RPC connection
The environment variables the Monero addon reads, how HTTP Digest authentication is negotiated against both daemons, why the backend re-points wallet-rpc at monerod itself, and what XMR_NETWORK actually controls.
The addon reads a short list of environment variables and nothing else. Everything about how it reaches Monero — which endpoints, which credentials, which network — is in this handful of keys, and every one of them is read once when the service is constructed. A change to any of them needs a backend restart.
The core keys
Both URLs are defaulted, so an install with none of these set will still try
127.0.0.1:18081 and 127.0.0.1:18083. That is usually right, and occasionally
the reason a misconfigured install appears to half-work.
A second daemon, and why the wallet is different
Substitute your network for MAINNET — XMR_STAGENET_RPC on stagenet, and so
on. Set neither and nothing changes: a pool of one endpoint is one attempt and
the same error you got before, byte for byte.
With two or more, order is not priority. The pool tries an endpoint it has
never used before any it has measured, and after that the fastest one, with a
20% margin before it switches. So the second daemon query in a process can go
to the second entry whether or not the first answered, and both daemons must be
on the same network and healthy — a stale or wrong-chain daemon anywhere in the
list will be dialled. XMR_DAEMON_RPC_URL is not consulted for daemon queries
once either list key is set, but it is still the address wallet-rpc is first
pointed at, until a pool daemon answers. The diagnostics probe every entry in
the list and say how many answered.
Only the daemon can fail over, and that is not an oversight. Monero is the one chain here with two RPCs, and they are not alike:
monerodanswers stateless node queries. Any healthy daemon answers them identically, so a list of them is meaningful.monero-wallet-rpcis a stateful process holding your opened wallet and its keys. A second one would be a second wallet. There is nothing to fail over to, and the backend does not try.
What the backend does instead is move the wallet's daemon. When the pool
switches, it issues set_daemon — the runtime equivalent of relaunching
wallet-rpc with --daemon-address — so the wallet syncs against the daemon that
is actually up.
If the backend read from a fallback daemon while wallet-rpc stayed on the dead one, the chain would report healthy — the backend can see a daemon — while no wallet could sync and every deposit quietly stopped being credited. That is worse than no failover at all, because the health check agrees with you.
If wallet-rpc refuses the set_daemon call, the daemon read still succeeds and
the backend logs a warning naming exactly this consequence. Search your logs for
could NOT be repointed.
A daemon that answers — a bad method, a bad parameter — is not treated as a dead endpoint. It stays in the pool and you get its error immediately, rather than the same malformed request being replayed against every daemon you own.
URL rules that are enforced
Three shapes are rejected outright by the diagnostics. The first two for the same reason — a bad URL would otherwise be echoed verbatim inside an error message and end up in a log or a support ticket; the third because it silently dials somewhere else entirely.
- A URL without a scheme. It must start with
http://. - A URL with credentials in it.
http://user:pass@127.0.0.1:18081/json_rpcis refused with "must not embed user:pass — use XMR_RPC_USER / XMR_RPC_PASSWORD instead". - An
https://URL with no port. The addon passes only the hostname, port and path to Node'shttpmodule — the scheme is never read and TLS is never negotiated — sohttps://host/json_rpcis dialled as plain HTTP on port 80, which is never the RPC. With an explicit port (https://host:18081/json_rpc) it does reach the daemon, because a stockmonerodruns--rpc-ssl autodetectand accepts plaintext on the same listener; the diagnostics allow that and warn, since the URL says TLS and the wire has none. Writehttp://and put a real TLS terminator in front if you need one.
Include the /json_rpc path. The Digest handshake signs the request URI, so a
truncated path does not merely 404 — it produces an authentication failure that
reads like wrong credentials.
How authentication actually works
Monero's RPC services use HTTP Digest, not Basic. The addon does not send
credentials pre-emptively. Every call is made anonymously first, and only when
the response is a 401 does it parse the WWW-Authenticate challenge and
retry with a Digest header.
Four consequences follow from that.
Unset credentials against an authenticated service produce a specific error.
If a 401 comes back and XMR_RPC_USER / XMR_RPC_PASSWORD are empty, the call
fails with "Monero daemon/wallet RPC requires authentication but
XMR_RPC_USER and XMR_RPC_PASSWORD are not configured", naming which of the two
services rejected it. That message is the fastest diagnosis you will get on this
chain — read it carefully.
Wrong credentials stop the deposit monitor immediately. A second 401 after the Digest retry raises an authentication failure, and the deposit monitor treats that as permanent: it unmonitors the wallet rather than retrying. Unlike a daemon outage, an auth fault does not heal itself.
Only the first Digest challenge is parsed, and only MD5. If a service offers
several challenges the addon uses the first, with algorithm=MD5. Stock
monerod and monero-wallet-rpc behave this way; a reverse proxy that
rewrites the challenge may not.
The challenge and the answer must travel on the same TCP connection.
monerod and monero-wallet-rpc keep the Digest session — the nonce and the
request counter — on the connection that issued the challenge, and treat a
valid answer arriving on any other connection as stale. The addon reuses its
keep-alive socket for the retry, so in normal operation this is invisible. It
stops being invisible behind a reverse proxy or load balancer that closes or
re-routes connections between the two requests: every call then fails with a
401 that reads like wrong credentials. The diagnostics tell the two apart. A
stale challenge is reported as "401 after Digest auth, challenge marked stale —
… did not see the challenge and the answer on the same TCP connection"; a real
mismatch as "401 after Digest auth — … rejected user …: XMR_RPC_USER /
XMR_RPC_PASSWORD do not match its --rpc-login". Earlier builds of the
diagnostics answered the challenge on a fresh connection themselves, and so
reported bad credentials against a pair the deposit monitor was using without
complaint — a false failure of the test, never of the chain.
Because there is one credential pair for two services, both must share the
same --rpc-login. There is no way to give the daemon and the wallet RPC
different accounts.
The backend owns the wallet RPC's daemon connection
monero-wallet-rpc only syncs against the daemon it was told to use. If it was
started without --daemon-address, or pointed somewhere else, wallet refreshes
fail with error -38, "no connection to daemon" even though the backend can
reach monerod perfectly well on its own.
So the addon takes ownership. At startup it calls set_daemon on the wallet RPC
with the host and port parsed out of XMR_DAEMON_RPC_URL, trusted: true,
ssl_support: "autodetect", and the same username and password if they are set.
It repeats that call, once, whenever a wallet refresh fails with a daemon error
(codes -38 or -9), then retries the refresh.
That recovers three common situations without operator intervention: the wallet
RPC starting before monerod is listening, a daemon restart, and a wallet RPC
launched with no daemon flags at all. It also means the daemon URL in .env is
authoritative — editing the systemd unit's --daemon-address without editing
.env changes nothing that lasts.
What XMR_NETWORK does and does not do
It does not select an endpoint, and this is the trap that catches people:
setting XMR_NETWORK=stagenet does not move you to stagenet. The network you
are on is whichever network monerod was started for.
It does name one — XMR_{NETWORK}_RPC above is spelled with it, so changing
XMR_NETWORK changes which daemon-list key is read. But that is the key's name,
not a switch: point XMR_STAGENET_RPC at a mainnet daemon and you are on
mainnet, with address validation now expecting stagenet prefixes.
What it does is decide which address prefixes a withdrawal destination may have:
XMR_NETWORK |
Accepted first characters |
|---|---|
mainnet |
4, 8 |
stagenet |
5, 7 |
testnet |
9, A, B |
An unrecognised value falls back to the mainnet prefix set. A destination that does not match is refused with "Invalid Monero address" before anything is signed.
Set XMR_NETWORK="testnet" against a mainnet daemon and every real customer
withdrawal address is rejected as invalid. Set it to mainnet against a
stagenet daemon and stagenet addresses are rejected instead.
The diagnostics catch this: they read nettype out of get_info and fail the
check with "XMR_NETWORK=… but the daemon runs … — valid withdrawal addresses
will be rejected". Run the test after any daemon change.
Deposit addresses are labelled differently and more defensively. When a wallet
is created, the network stamped on the address record is derived from the
address prefix returned by the wallet RPC, falling back to XMR_NETWORK
only if the prefix is unrecognised. The wallet's own output is treated as ground
truth, because XMR_NETWORK can be wrong or can change after addresses were
issued.
Timeouts, retries and back-off
Worth knowing before you tune anything upstream, because Monero calls are slow by nature and a proxy with a short idle timeout will break them.
- Ordinary RPC calls: 30 second timeout, up to 3 attempts, one second apart.
- Wallet refresh: 60 seconds by default. A background refresh of a dormant wallet gets 10 minutes. A withdrawal opens its wallet with 2 minutes if the wallet was refreshed recently, or 15 minutes if it has not been touched in over six hours.
- Balance reads refresh with a 120 second budget before returning a number.
- The withdrawal relay is deliberately 1 attempt, 120 seconds. Retrying a relay could broadcast a second transaction.
When the daemon is unreachable the monitoring loop applies exponential back-off — 30 s, then 60, 120, capped at five minutes — instead of hammering a wallet RPC that is itself blocked waiting on the daemon. Daemon errors do not count against a wallet's retry budget, so a monitor survives an outage and resumes.
Keys that do nothing
Set these and nothing changes. They circulate in copied .env files and cost
people hours during debugging.
| Key | Reality |
|---|---|
XMR_WALLET_PASSWORD |
Read by no code. Wallets are created and opened with an empty password regardless. The diagnostics raise a warning when it is set, because its presence implies a protection that does not exist |
XMR_WALLET_USER |
Listed in the admin diagnostics, but read by no runtime code |
Verifying by hand
curl -s -X POST http://127.0.0.1:18083/json_rpc \
-H 'Content-Type: application/json' \
--digest -u "$XMR_RPC_USER:$XMR_RPC_PASSWORD" \
-d '{"jsonrpc":"2.0","id":"0","method":"get_version"}'curl -s -X POST http://127.0.0.1:18081/json_rpc \
-H 'Content-Type: application/json' \
--digest -u "$XMR_RPC_USER:$XMR_RPC_PASSWORD" \
-d '{"jsonrpc":"2.0","id":"0","method":"get_info"}'If get_version answers and get_info reports "synchronized": true on the
nettype you configured, the connection layer is correct and any remaining
problem is elsewhere. The same two probes are what Admin → Ecosystem →
Blockchains → Requirements runs for you.