Configuring the RPC connection

The five 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.

5 min readUpdated 3 August 2026rpc, monerod, wallet-rpc, digest-auth, network

The addon reads five environment variables and nothing else. Everything about how it reaches Monero — which endpoints, which credentials, which network — is in this handful of keys, and all five are read once when the service is first constructed. A change to any of them needs a backend restart.

The five keys

monerod JSON-RPC URL, including the /json_rpc path. Used for fee estimation and sync status, and forwarded to wallet-rpc as its daemon address.
monero-wallet-rpc JSON-RPC URL, including the /json_rpc path. Every wallet operation goes through it. If get_version fails at startup the whole chain is disabled.
Username for HTTP Digest auth against both the daemon and the wallet RPC, and the daemon login forwarded in set_daemon. Leave unset only if both services run with --disable-rpc-login.
Password paired with XMR_RPC_USER.
Declares which Monero network this install operates on. Selects no endpoint — the real network is whatever monerod runs. Drives withdrawal address-prefix validation.

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.

URL rules that are enforced

Two shapes are rejected outright by the diagnostics, and both are rejected 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.

  • A URL without a scheme. It must start with http:// or https://.
  • A URL with credentials in it. http://user:pass@127.0.0.1:18081/json_rpc is refused with "must not embed user:pass — use XMR_RPC_USER / XMR_RPC_PASSWORD instead".

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.

Three 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.

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. There is no XMR_MAINNET_RPC in the code path; the network you are on is whichever network monerod was started for.

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
XMR_MAINNET_RPC and siblings Display only. The Ecosystem overview badge checks whether XMR_<NETWORK>_RPC is present; the Monero service never reads it

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.