How to Set Up a Solana RPC Cluster: Step-by-Step
A Solana RPC cluster takes the same load-balanced pool pattern from EVM chains and adapts it to Solana's much heavier hardware demands. Instead of one node serving every request, we distribute traffic across multiple Agave RPC nodes behind a load balancer. In this guide, we'll walk through what makes a Solana cluster different from EVM clusters, what hardware is actually required, and how to wire it all together.
#What is a Solana RPC Cluster?
Quick terminology note before we start: in Solana's own documentation, "cluster" refers to the network itself, Mainnet-Beta, Testnet, and Devnet are each "clusters" of validators. What we're building is a private RPC pool that sits in front of one of those networks, the same pattern we'd use for any dedicated RPC node deployment, just adapted for Solana's specific demands.
A Solana RPC cluster is a group of synchronized non-voting Agave nodes sitting behind a load balancer that routes incoming JSON-RPC requests across them. Each backend runs the same agave-validator software a consensus validator would, just configured with --no-voting and --full-rpc-api so it serves data instead of voting on blocks. The load balancer, usually NGINX, distributes requests based on health checks and current load.
The reasons to cluster Solana RPC nodes are the same as for any chain: redundancy, throughput, and zero-downtime maintenance. The difference is the cost of getting it wrong. A single Solana RPC node under heavy getProgramAccounts traffic can saturate hundreds of GB of RAM and still return stale data. Distributing that load across multiple nodes is the only realistic way to serve real applications.
Set up your Solana server in minutes
Optimize cost and performance with a pre-configured or custom dedicated bare metal server for blockchain workloads. High uptime, instant 24/7 support, pay in crypto.
#Solana RPC Cluster Requirements
Solana RPC requirements diverge sharply from EVM chains. The hardware bar is much higher because each node has to keep up with Solana's 400ms slot times while simultaneously serving heavy concurrent queries.
#Cluster Hardware Requirements
These specs are based on the official Anza validator requirements with the additional RPC node overhead applied:
- 2-3 Backend RPC Nodes
- CPU: 16 cores / 32 threads minimum (24 cores / 48 threads recommended for production), 2.8 GHz base or higher, AVX2 + SHA extensions required, AMD Gen 3 or newer / Intel Ice Lake or newer
- RAM: 256 GB minimum, 512 GB+ if running account indexes (`getProgramAccounts` heavy workloads)
- Storage: PCIe Gen3 x4 NVMe minimum on separate disks, 1 TB+ for accounts, 1 TB+ for ledger, 500 GB+ for snapshots. Accounts and ledger *should not* share a disk
- Network: 1 Gbit/s symmetric minimum, 10 Gbit/s preferred for mainnet-beta
- 1-2 Load Balancer Instances
- CPU: 4-8 cores
- RAM: 8-16 GB
- Storage: 100 GB SSD
- Network: 1 Gbit/s symmetric
Higher clock speed is preferable to more cores on Solana, and single-socket CPUs outperform dual-socket configurations because NUMA latency hurts query performance. Consumer NVMe drives also degrade fast under Solana's sustained write load, so enterprise-grade drives with high TBW (Total Bytes Written) ratings are effectively mandatory for production. Solana itself does not currently use GPUs, so there's no need to factor those in.
For this tutorial, we used Cherry Servers' Solana Server Gen5 as our backend node baseline. It's purpose-built for this workload, AMD EPYC 9355 on Zen 5, 768 GB DDR5 RAM, and 2x 1TB + 2x 4TB NVMe across separate drives, exactly the split-disk layout Anza recommends. For lighter deployments without full account indexes, the AMD EPYC 9375F at 32 cores, 384 GB RAM, and the same NVMe layout works well.
#Cluster Software Requirements
- Ubuntu 24.04 LTS (the version Anza builds and tests against)
- SSH access with a sudo user on every machine
- The Agave validator client (we'll install this in Step 1)
- NGINX for load balancing
- Prometheus and Grafana for monitoring (strongly recommended)
#Ports to Open
Solana uses a wide P2P port range compared to EVM chains. Backend nodes expose RPC ports *only* to the load balancer.
Backend Nodes
| Port | Protocol | Purpose |
|---|---|---|
| 8000-10000 | TCP/UDP | Solana P2P (gossip, turbine, repair) |
| 8899 | TCP | JSON-RPC HTTP (to load balancer only) |
| 8900 | TCP | JSON-RPC WebSocket (to load balancer only) |
Load Balancer
| Port | Protocol | Purpose |
|---|---|---|
| 443 | TCP | HTTPS (public) |
| 80 | TCP | HTTP (public, for redirects) |
Pro Tip: We use `--dynamic-port-range` in the startup script to narrow the P2P range from the default 8000-10000 down to a smaller window that matches our firewall rules. Agave requires at least a 25-port range to bind reliably on startup, so we'll use 8000-8025 in this guide.
#Node Roles: Accounts vs. Full Nodes in a Cluster
Unlike EVM clusters where every backend is identical, Solana RPC clusters typically split nodes by role. The reason is that account-data RPC requests like getProgramAccounts and getTokenAccountsByOwner scan the entire account set, which can consume hundreds of GB of RAM when account indexes are enabled. Putting that load on a node also serving simple getBalance requests means everything slows down together.
A common production split looks like this:
Accounts nodes run with `--account-index program-id spl-token-mint spl-token-owner` enabled. These have the largest RAM allocation (768 GB-1 TB) and exclusively handle heavy queries. The indexes keep account data memory-resident, so lookups are fast even when the underlying account set is huge.
Full nodes run without account indexes and handle the bulk of standard read traffic, transaction submission via `sendTransaction`, and WebSocket subscriptions. They need less RAM (256-512 GB) and can be scaled out horizontally more cheaply.
Both node types run the same agave-validator binary with the same --no-voting and --full-rpc-api flags. The difference is which requests the load balancer routes to which pool, plus the --account-index flag and RAM allocation. We'll wire this routing up in Step 4.
#How to Set Up a Solana RPC Cluster
We'll build a cluster with two full nodes and one accounts node behind NGINX. The same pattern works at any scale, just add or remove backends from the upstream pool.
#Step 1: Install Agave on Each Backend Node
The Anza install tool handles versioning cleanly. Replace <version> with the latest stable release tag from the Agave releases page, or use the stable channel name to always pull the current production release.
Run this on each backend node:
sh -c "$(curl -sSfL https://release.anza.xyz/<version>/install)"
After the install, update your PATH if prompted, then verify:
agave-validator --version
The official Anza setup guide assumes you'll create a dedicated sol user, generate a validator-keypair.json identity file (RPC nodes don't vote but still need an identity for gossip), mount the ledger and accounts on separate NVMe drives, and apply system tuning (sysctl knobs, file descriptor limits). The full procedure is covered in our Solana RPC node setup guide, follow that guide for each of your backend nodes before continuing here.
#Step 2: Configure the Backend Startup Script
On each backend node, create a validator.sh startup script in /home/sol/bin/ (Anza's convention). Below is a mainnet-beta RPC node configuration based on the official Anza RPC node example, modified for cluster use.
For full nodes (no account indexes):
#!/bin/bash
exec agave-validator \\
--identity /home/sol/validator-keypair.json \\
--known-validator 7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2 \\
--known-validator GdnSyH3YtwcxFvQrVVJMm1JhTS4QVX7MFsX56uJLUfiZ \\
--known-validator DE1bawNcRJB9rVm3buyMVfr8mBEoyyu73NBovf2oXJsJ \\
--known-validator CakcnaRDHka2gXyfbEd2d3xsvkJkqsLw2akB3zsN1D2S \\
--only-known-rpc \\
--full-rpc-api \\
--no-voting \\
--ledger /mnt/ledger \\
--accounts /mnt/accounts \\
--log /home/sol/solana-rpc.log \\
--rpc-port 8899 \\
--rpc-bind-address 10.0.0.10 \\
--private-rpc \\
--dynamic-port-range 8000-8025 \\
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \\
--entrypoint entrypoint2.mainnet-beta.solana.com:8001 \\
--entrypoint entrypoint3.mainnet-beta.solana.com:8001 \\
--entrypoint entrypoint4.mainnet-beta.solana.com:8001 \\
--entrypoint entrypoint5.mainnet-beta.solana.com:8001 \\
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \\
--wal-recovery-mode skip_any_corrupted_record \\
--limit-ledger-size
For accounts nodes, add the index flag right above --wal-recovery-mode:
--account-index program-id \\
--account-index spl-token-mint \\
--account-index spl-token-owner \\
A few things to call out about this config:
--rpc-bind-address 10.0.0.10binds the RPC to the internal network IP. Replace this with each node's actual private IP. We bind to internal-only because the load balancer is the only thing that should reach 8899 directly.--private-rpcprevents the node from advertising its RPC port over Solana's gossip protocol, so it won't show up insolana gossiplistings or attract random external traffic.--only-known-rpcrestricts which validators the node accepts snapshots from, preventing snapshot poisoning attacks. The known validator pubkeys are the canonical mainnet-beta operators.--limit-ledger-sizeprunes the ledger so disk usage stays bounded. With no value, Anza attempts to keep the blockstore under 500 GB. To set a specific limit, pass a number of shreds (e.g.,--limit-ledger-size 50000000for ~50 million shreds). Without this flag, the ledger fills your disk in weeks. If your application needs deeper history, hook up a Solana BigTable instance.
Paying too much for cloud infrastructure?
Switch to blockchain-optimized dedicated bare metal—save up to 60% on your cloud bill and double the performance compared to hyperscale cloud.
#Step 3: Configure the Backend Firewalls
On each backend, allow Solana's P2P range and only the load balancer's IP for RPC traffic:
sudo ufw allow 8000:8025/tcp
sudo ufw allow 8000:8025/udp
sudo ufw allow from <load-balancer-ip> to any port 8899 proto tcp
sudo ufw allow from <load-balancer-ip> to any port 8900 proto tcp
sudo ufw enable
The 8000-8025 range matches the --dynamic-port-range flag in the startup script. If you change one, change the other.
#Step 4: Configure NGINX for Load Balancing
On the load balancer, install NGINX:
sudo apt-get update
sudo apt-get install \-y nginx
Two files need editing. First, add the WebSocket upgrade map at the top of the http block in /etc/nginx/nginx.conf:
map $http\_upgrade $connection\_upgrade {
default upgrade;
'' close;
}
Now create the cluster site config:
sudo nano /etc/nginx/sites-available/solana-rpc
Paste in the following, adjusting the backend IPs to match your nodes:
upstream solana\_full\_nodes {
least\_conn;
server 10.0.0.10:8899 max\_fails=2 fail\_timeout=30s;
server 10.0.0.11:8899 max\_fails=2 fail\_timeout=30s;
keepalive 64;
}
upstream solana\_accounts\_nodes {
least\_conn;
server 10.0.0.12:8899 max\_fails=2 fail\_timeout=30s;
keepalive 32;
}
upstream solana\_ws\_nodes {
least\_conn;
server 10.0.0.10:8900 max\_fails=2 fail\_timeout=30s;
server 10.0.0.11:8900 max\_fails=2 fail\_timeout=30s;
}
map $request\_body $upstream\_pool {
default solana\_full\_nodes;
"~*getProgramAccounts" solana\_accounts\_nodes;
"~*getTokenAccountsByOwner" solana\_accounts\_nodes;
"~*getTokenAccountsByDelegate" solana\_accounts\_nodes;
}
server {
listen 443 ssl http2;
server\_name rpc.yourdomain.com;
client\_body\_buffer\_size 1m;
ssl\_certificate /etc/ssl/certs/rpc.crt;
ssl\_certificate\_key /etc/ssl/private/rpc.key;
location / {
proxy\_pass http://$upstream\_pool;
proxy\_http\_version 1.1;
proxy\_set\_header Connection "";
proxy\_set\_header Host $host;
proxy\_set\_header X-Real-IP $remote\_addr;
proxy\_connect\_timeout 5s;
proxy\_read\_timeout 60s;
}
location /ws {
proxy\_pass http://solana\_ws\_nodes;
proxy\_http\_version 1.1;
proxy\_set\_header Upgrade $http\_upgrade;
proxy\_set\_header Connection $connection\_upgrade;
proxy\_set\_header Host $host;
proxy\_set\_header X-Real-IP $remote\_addr;
proxy\_read\_timeout 3600s;
}
}
The key part here is the map $request_body block. NGINX inspects each incoming JSON-RPC payload and routes calls containing getProgramAccounts, getTokenAccountsByOwner, or getTokenAccountsByDelegate to the dedicated accounts pool. Everything else goes to the full nodes. We raise client_body_buffer_size to 1 MB because NGINX only populates $request_body when the body fits in memory, large filtered queries would otherwise spill to a temp file and fall through to the default pool.
Note that this routing is best-effort: batched JSON-RPC requests (an array of calls) route as a single unit based on the first method match, so split your traffic at the client level if precise routing matters. We use least_conn instead of round-robin because Solana query times vary widely between methods, and connection-count balancing handles that variance better.
Enable the config and reload:
sudo ln \-s /etc/nginx/sites-available/solana-rpc /etc/nginx/sites-enabled/
sudo nginx \-t
sudo systemctl reload nginx
#Managing Data Consistency and Load Balancing
Solana's slot times are around 400ms, so a node that's only one or two slots behind is effectively fine. But if it falls 50+ slots behind, it'll start returning stale data and breaking application assumptions about state freshness. NGINX's max_fails only catches outright connection failures, not silent slot lag.
The standard fix is an external health check script that calls getSlot on each backend every 15-30 seconds and compares against the network head (using a known healthy node or a public endpoint as the reference). If a backend falls more than 50 slots behind, the script removes it from the upstream pool by editing the NGINX config and reloading.
For commitment level consistency, set the level explicitly in your client code rather than relying on node defaults. If one backend returns processed while another returns confirmed, your application sees flickering state. The three commitment levels are: processed (latest seen, may not be confirmed), confirmed (supermajority of validator votes), and finalized (rooted, irreversible). Most applications should use confirmed for reads and finalized for anything involving money.
#Securing and Monitoring the Solana Endpoint
Use Let's Encrypt for TLS certificates:
sudo apt-get install \-y certbot python3-certbot-nginx
sudo certbot \--nginx \-d rpc.yourdomain.com
For rate limiting, add this to /etc/nginx/nginx.conf:
limit\_req\_zone $binary\_remote\_addr zone=solana\_limit:10m rate=200r/s;
Then in the cluster site config, inside the server block:
limit\_req zone=solana\_limit burst=400 nodelay;
Solana RPC traffic patterns include bursts of WebSocket subscriptions and heavy getProgramAccounts calls, so set the burst allowance higher than you would for EVM chains.
For monitoring, the standard stack is Prometheus scraping each Agave node's metrics endpoint with Grafana for visualization. At minimum, track: slot lag per node (the most important metric), getProgramAccounts latency p95, RAM usage on accounts nodes, NVMe write saturation, and load balancer error rates. The Solana node exporter project provides ready-made Grafana dashboards for most of these.
Need help choosing the right infrastructure?
✅ Talk to an expert in 15 minutes. We'll advise what’s best for your business needs.
FAQs
How many nodes do I need in a Solana cluster?
For production, three is the working minimum: two full nodes and one accounts node. This lets you take any single node out for maintenance without losing redundancy on either tier.
Should I use Agave or Firedancer?
Agave is the production-ready client for RPC nodes today. Firedancer (and its hybrid Frankendancer build) is gaining adoption among consensus validators, but Agave has the most mature RPC tooling and feature set for serving JSON-RPC traffic.
Do I need Yellowstone gRPC or Geyser?
Only if your application needs real-time streaming of state changes. For standard RPC traffic, Agave's built-in WebSocket subscriptions are enough. Geyser plugins like Yellowstone are for high-throughput data pipelines that would otherwise overwhelm a normal RPC connection.
Can I use cloud VMs for Solana RPC nodes?
Anza explicitly notes in their documentation that running Agave nodes in the cloud requires significantly greater operational expertise to achieve stability, and that you should not expect sympathetic support if things go wrong. Bare metal is effectively required for production.
How fast does the ledger grow?
Around 120 TB per year on mainnet-beta. The `--limit-ledger-size` flag is essential for RPC nodes, without it, you'll fill your ledger disk in weeks. Set it based on how much historical data your application actually needs to query directly. For deeper history, hook up a Solana BigTable instance.
We accept Bitcoin and other popular cryptocurrencies.