Troubleshooting

Diagnosing Monero faults — the daemon that is reachable but not synced, the wallet RPC that is the hard gate, wallets that will not open, deposits that never credit and withdrawals that hold or time out.

9 min readUpdated 3 August 2026troubleshooting, sync, daemon, wallet-rpc, deposits, withdrawals

Almost every Monero fault is one of four things: the daemon is not synced, the wallet RPC is not reachable, the credentials do not match, or the wallet RPC is busy with something else. Work down that list before anything more exotic.

Fast triage

Run these three in order. The first one that fails is your answer.

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"}'
pm2 logs backend --lines 400 | grep XMR

Then run the in-product diagnostics, which perform the same probes and add the licence, master-wallet and service-module checks: Admin → Ecosystem → Blockchains → Requirements → Monero → Test.

Symptom Look at
Chain will not enable, 403 Licence file lic/54578959.lic
Enabled but every call says "service not available" Wallet RPC get_version
Deposits never appear Daemon sync, then the six-confirmation clock
Balance reads 0 with funds on chain Wallet refresh — is the daemon reachable?
"Invalid Monero address" for a good address XMR_NETWORK against the daemon's nettype
Withdrawal sits PENDING Locked outputs — usually normal
Withdrawal is TIMEOUT Manual review. Do not refund without checking

Diagnosis in detail

The chain stays inactive with a valid licence

Three gates run at startup, in order: the licence file, the ecosystem_blockchain row's status, and a get_version call against the wallet RPC. Only the third fails silently from the admin UI's point of view — the row shows enabled while the service refuses everything with "Monero service not available. Please ensure your license is activated, the blockchain is enabled, and the daemon is synchronized."

If probe 1 above answers, but the chain is still inactive, check the fourth prerequisite the diagnostics test: Chain service installed. That verifies the addon's code module exists in the install. If it reports "not installed", the product's files were never extracted onto the server and no amount of configuration will help.

Licence results are cached for five minutes, so a freshly activated licence can take that long to take effect without a restart.

"Monero daemon not reachable. Wallet cannot sync."

The wallet RPC could not reach monerod. Note the asymmetry: the backend may be able to reach the daemon perfectly well and still see this, because monero-wallet-rpc maintains its own separate connection.

The addon tries to repair it. On this error — codes -38 ("no connection to daemon") or -9 ("daemon is busy") — it re-issues set_daemon with the host and credentials from .env and retries the refresh once. If it still fails, check in this order:

  1. monerod is running and answering probe 2.
  2. XMR_RPC_USER / XMR_RPC_PASSWORD match the daemon's --rpc-login. The same pair is used for the wallet RPC and forwarded as the daemon login, so one mismatched password breaks this specific path while the wallet RPC itself appears healthy.
  3. The wallet RPC can actually route to the daemon's host and port — a containerised wallet RPC pointed at 127.0.0.1 is pointed at itself.

While this condition persists the monitoring loop backs off exponentially, from 30 seconds up to five minutes, and daemon errors are not counted against a wallet's retry budget. Monitors survive an outage and resume; you do not need to restart anything once the daemon is back.

"requires authentication but XMR_RPC_USER and XMR_RPC_PASSWORD are not configured"

A service returned 401 and there are no credentials to answer with. The message names which service — daemon or wallet — so read it rather than guessing.

Either set both variables to match the --rpc-login you configured, or start both services with --disable-rpc-login and leave the variables unset. What does not work is authenticating one service and not the other.

Bad credentials stop deposit monitoring permanently. A 401 that survives the Digest retry raises "Monero RPC authentication failed. Invalid credentials", and the deposit monitor treats that as fatal: it removes the wallet from monitoring rather than retrying. Unlike a daemon outage, this does not heal when you fix it — restart the backend after correcting the credentials.

The diagnostics say "401 after Digest auth" while deposits and withdrawals work

Read the rest of the line — it names one of two different faults.

"challenge marked stale — … did not see the challenge and the answer on the same TCP connection." The credentials were accepted: monerod only marks a challenge stale after the username matched and the digest verified. What it rejected was the nonce, which it keeps on the connection that issued it — something between the backend and the service (a reverse proxy, a load balancer, a tunnel that closes idle connections) delivered the answer on a different one. Point the key the failing row is about — the daemon row is titled with its source key, XMR_<NETWORK>_RPC when that is set and XMR_DAEMON_RPC_URL otherwise, and the wallet row is always XMR_WALLET_RPC_URL — at the service directly, or make the proxy keep one upstream connection per client connection. The addon's own calls fail the same way through that path.

"rejected user …: XMR_RPC_USER / XMR_RPC_PASSWORD do not match its --rpc-login." The service judged the pair and refused it. Both services must share the same --rpc-login; check the one the row names.

Earlier builds of the diagnostics produced the first fault themselves — the probe answered the challenge on a fresh connection — and reported it as the second, against credentials the deposit monitor was using without complaint. The chain was healthy; the test was wrong. On such a build, the curl --digest calls at the top of this page are the authoritative check.

"Failed to open wallet"

In order of likelihood:

  • Another wallet is already open. The addon detects this, closes whatever is open and retries once. If you see it repeatedly, something outside the platform is using the same wallet RPC — a monero-wallet-cli session, a script, a second backend process. One wallet RPC, one consumer.
  • The wallet RPC was not started with --wallet-dir. Wallets are opened by filename. With --wallet-file instead, only that one wallet exists.
  • The wallet file has a password. The addon always passes an empty password; XMR_WALLET_PASSWORD is read by nothing. A wallet created with a password by external tooling cannot be opened.
  • Wrong directory or wrong permissions. The service user must own the wallet directory.
[CRITICAL] Wrong wallet context!

The addon asserts, before every read and every transfer, that the wallet it believes is open is the wallet that is actually open. This error means the assertion failed — the wallet RPC is being driven by something other than this backend, or by two backend processes at once.

Stop and find the second consumer before doing anything else. This guard exists to prevent reading one customer's balance while another customer's wallet is open, which is the worst possible failure mode on a custody chain.

A balance reads 0 while the funds are visibly on chain

A freshly opened wallet answers from its on-disk cache. The addon refreshes before every balance read — with a 120-second budget — precisely to avoid this, so a zero here means the refresh did not complete: an unreachable daemon, an unsynced daemon, or a wallet so far behind that 120 seconds was not enough.

Check probe 2 first. If the daemon is fine, give the wallet time — a long-dormant wallet's first sync is slow, and the background refresh will pick it up once the queue is idle.

Deposits are never detected

Work through these:

  1. Six confirmations have not passed. Roughly twelve to twenty minutes. This is the answer more often than anything else.
  2. The daemon is not synced. get_transfers against an unsynced wallet returns an incomplete picture and no error.
  3. The monitor stopped. A monitor ends after three consecutive empty checks once it has run for ten minutes, and unconditionally at ninety. If the deposit arrived after that, only the background scanner will find it.
  4. The background scanner is disabled. ECOSYSTEM_BACKGROUND_SCAN="false" removes the only path that catches a deposit made after the deposit page was closed. On a Monero install, leave it on.
  5. The address was never registered for scanning. The working set is populated when a user subscribes to the deposit WebSocket, with a 72-hour TTL. A deposit sent to an address the user has not visited in over three days is found only when they open the page again.

Re-opening the deposit page is the supported way to force a re-check: it re-registers the address and starts a live monitor.

Deposits are detected but arrive late

The Monero background sweep rate is one wallet every twenty seconds, and every sweep queues behind live sessions. On a busy install with many watched wallets, a deposit found by the scanner rather than by a live monitor can be minutes behind the chain. That is the cost of a single serialised wallet RPC, and it is deliberate — raising ECOSYSTEM_SCAN_RATE_XMR starves the live sessions that customers are actually watching.

"Invalid Monero address" for an address the customer says is correct

XMR_NETWORK decides which prefixes are accepted: mainnet 4/8, stagenet 5/7, testnet 9/A/B. If it disagrees with the network monerod actually runs, every genuine address is rejected.

The diagnostics compare the two and fail with "XMR_NETWORK=… but the daemon runs … — valid withdrawal addresses will be rejected". Fix .env, restart the backend, re-run the test.

A withdrawal is stuck at PENDING

Read the transaction's description. If it says "Funds are locked (x/y XMR unlocked). Waiting to process", the withdrawal is working as designed: Monero locks received outputs for about ten blocks, and the queue watchdog will retry. The hold is bounded at two hours from the row's creation, after which it fails and refunds.

If the description says something else, see Withdrawals for the full state machine.

A withdrawal is TIMEOUT, or stuck PROCESSING with no hash

Neither of these is refunded automatically, and that is correct — the transaction may already be on the Monero network, and paying the customer twice is worse than a delay.

Resolve by hand. Open the customer's wallet (its filename is the ECO wallet's UUID) in a Monero client, or query the wallet RPC for its outgoing transfers, and look for a send matching the amount and destination around that timestamp. If it exists, complete the row with the transaction hash. If it does not, refund and mark it failed.

Do not re-run the withdrawal. The final relay is issued exactly once precisely because a second attempt could broadcast a second payment.

The daemon will not finish syncing

Disk and I/O, almost always. Check free space on the data-dir partition first. Then consider --prune-blockchain, which substantially reduces the on-disk size and still supports wallet syncing.

If memory is the constraint, lower max-concurrency in bitmonero.conf and add swap. A daemon that is being OOM-killed and restarted by systemd every few hours never completes an initial sync.

Logs worth grepping

# Everything the Monero service emits
pm2 logs backend --lines 500 | grep XMR

# Daemon and wallet RPC
sudo journalctl -u monerod -n 200
sudo journalctl -u monero-wallet-rpc -n 200

A healthy start prints, in order: the wallet RPC version, the daemon height and sync flag, the daemon the wallet RPC was pointed at, and "Monero service initialized successfully".

When to escalate

Escalate — rather than retrying — when a withdrawal is TIMEOUT or stale PROCESSING, when you see Wrong wallet context, or when a wallet file is missing from the wallet directory. All four involve customer funds and none of them are made better by another attempt.