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.
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 XMRThen 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
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.
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:
monerodis running and answering probe 2.XMR_RPC_USER/XMR_RPC_PASSWORDmatch 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.- The wallet RPC can actually route to the daemon's host and port — a
containerised wallet RPC pointed at
127.0.0.1is 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.
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.
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-clisession, 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-fileinstead, only that one wallet exists. - The wallet file has a password. The addon always passes an empty password;
XMR_WALLET_PASSWORDis 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.
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 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.
Work through these:
- Six confirmations have not passed. Roughly twelve to twenty minutes. This is the answer more often than anything else.
- The daemon is not synced.
get_transfersagainst an unsynced wallet returns an incomplete picture and no error. - 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.
- 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. - 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.
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.
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.
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.
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.
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 200A 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.