How to Health-Check a Solana RPC Endpoint Before You Commit
A practical Node.js 18+ script that verifies a Solana RPC endpoint's chain identity, slot freshness, and error behavior before you route production traffic to it, plus how to separate RPC access from indexing.
Enterprise teams evaluating Solana RPC providers usually start with a vendor comparison, but the first thing you actually need is a repeatable check you can run against any endpoint: does it serve the right cluster, is its slot head moving, and does it fail cleanly when you send bad input? This guide gives you a small Node.js 18+ script that answers those questions with your own measurements, so provider claims become something you verify rather than something you trust. It also draws the line between RPC access and indexing, because the two solve different problems and are often conflated in procurement conversations.
What an RPC health check should actually verify
A Solana JSON-RPC endpoint is a transport to a cluster, not a database of your program's history. Before you compare providers, decide what you need from the connection: correct cluster identity, a slot head that advances, sane latency for the methods you call, and predictable error responses. Those are things you can measure from your own machine or CI runner in a few seconds.
Three calls cover most of the ground. getGenesisHash confirms which cluster you are talking to, so a devnet key never silently lands in a mainnet pipeline. getSlot gives you a head reference you can sample twice to see movement. getHealth returns a coarse status. None of these prove throughput under your workload, so treat them as a gate, not a benchmark.
If you are still choosing a network entry point, the OnFinality Solana network page lists the cluster endpoints they publish; use those values as input to the script below rather than hardcoding anything from this article.
- Cluster identity via getGenesisHash prevents cross-cluster mistakes
- Two getSlot samples show whether the head is advancing
- getHealth is a coarse signal, not a load test
- Latency and error shape must be measured by you, per method
A runnable Node.js 18+ health check
The script below uses the built-in fetch available in Node.js 18 and later, so there are no dependencies. It reads the endpoint from an environment variable, runs the three checks, and exits non-zero if any check fails, which makes it usable as a CI gate or a pre-deploy smoke test.
Pass the endpoint you want to evaluate. If you do not yet have one, the OnFinality Solana network page is where their published endpoints live; copy the URL from there into the variable. Do not paste endpoints into source control.
- No dependencies: uses global fetch in Node.js 18+
- Exits non-zero on failure so CI can block a bad endpoint
- Reads the endpoint from the environment, never hardcoded
// health-check.mjs — Node.js 18+
const ENDPOINT = process.env.SOLANA_RPC_URL;
if (!ENDPOINT) {
console.error('Set SOLANA_RPC_URL to the endpoint you want to check.');
process.exit(2);
}
async function rpc(method, params = []) {
const started = Date.now();
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
});
const elapsed = Date.now() - started;
if (!res.ok) {
throw new Error(`${method}: HTTP ${res.status} after ${elapsed}ms`);
}
const body = await res.json();
if (body.error) {
throw new Error(`${method}: RPC error ${body.error.code} ${body.error.message}`);
}
return { result: body.result, elapsed };
}
async function main() {
const genesis = await rpc('getGenesisHash');
console.log('genesis hash:', genesis.result, `(${genesis.elapsed}ms)`);
const health = await rpc('getHealth');
console.log('health:', health.result, `(${health.elapsed}ms)`);
const first = await rpc('getSlot');
await new Promise((r) => setTimeout(r, 4000));
const second = await rpc('getSlot');
console.log('slot samples:', first.result, '->', second.result);
if (second.result < first.result) {
throw new Error('Slot head went backwards; endpoint may be on a different fork or cluster.');
}
if (second.result === first.result) {
console.warn('Slot head did not advance in 4s. Re-sample before drawing conclusions.');
}
console.log('OK');
}
main().catch((err) => {
console.error('FAILED:', err.message);
process.exit(1);
});
Run it with SOLANA_RPC_URL=<your endpoint> node health-check.mjs. A healthy run prints a genesis hash, a health string, two slot numbers, and OK.
The genesis hash is the value to pin in your config. Compare it against a known-good source for the cluster you intend to use; if it differs, stop and fix the endpoint before anything else.
Expected result and how to handle failures
A successful run looks like three labelled lines and a final OK. The exact slot numbers and millisecond figures are specific to your machine, your network path, and the moment you ran it, so record them rather than comparing them to numbers in any article, including this one.
Failures fall into a few shapes and each means something different. An HTTP 401 or 403 usually means a missing or invalid API key. A JSON-RPC error object with a negative code means the endpoint understood you and rejected the request, which is often a parameter problem. A connection reset or timeout points at the network path or at rate limiting. A slot head that does not advance across several samples deserves a re-test before you conclude anything.
Handle these distinctly in your own code. Retry timeouts with backoff, but do not retry an auth failure in a loop; surface it. Log the method name and elapsed time on every failure so you can tell a slow endpoint from a broken one.
- 401/403: credential problem, do not retry blindly
- JSON-RPC error object: request rejected, check parameters
- Timeout or reset: network path or rate limit, retry with backoff
- Static slot head: re-sample several times before judging
# Example failure output shapes (illustrative, not measured)
# FAILED: getGenesisHash: HTTP 401 after 120ms
# FAILED: getSlot: RPC error -32602 Invalid params
# FAILED: getHealth: fetch failed
Because these are illustrative shapes rather than real captures, run the script against your own endpoint to see which one you hit. The OnFinality RPC Assistant is a reasonable place to sanity-check method names and parameters if a call is being rejected.
RPC access is not indexing: keep the two separate
An RPC endpoint answers point questions about current and recent chain state: what is this account's balance, what is in this transaction, what is the current slot. It is not a query engine over your program's full history. If you need to ask questions like "every swap by this wallet last month" or "all accounts owned by my program matching a filter," you are describing an indexing problem, and no amount of RPC tuning will turn it into one.
This distinction matters during procurement. A provider can give you excellent RPC access and still not answer historical aggregate queries, because those require something that has walked and stored the chain. Conversely, an indexer is not a substitute for a low-latency endpoint your wallet or bot calls on every interaction.
Practically: use RPC for reads your application makes at request time, and use an indexer or your own pipeline for analytics, backfills, and history-dependent features. If you are unsure which side a requirement falls on, the OnFinality Learn library has background material on how these pieces fit together.
- RPC: point lookups against current and recent state
- Indexing: historical and aggregate queries over stored data
- A fast endpoint does not make historical queries cheap
- Budget for both if your product needs both
Turn the check into a habit
Run the health check whenever you change endpoints, rotate keys, or promote a configuration between environments. Keep the genesis hash assertion in your test suite so a misconfigured environment fails fast instead of producing confusing application errors later.
If you want to go further, extend the script to time the specific methods your application actually calls, since a fast getSlot says little about getProgramAccounts or sendTransaction under your load. Measure those yourself, on your network path, with your payloads.
For a broader walkthrough of Solana RPC concepts and tooling, the OnFinality Learn library is a sensible next read, and the OnFinality RPC Assistant can help you construct and validate individual calls.
- Re-run on endpoint changes and key rotations
- Pin the genesis hash in tests
- Time the methods your app actually uses, not just getSlot