RPC Security and Abuse Control
Design a public Solana RPC service without making a validator, a shared backend, or an upstream dependency the blast radius.
RPC security is the set of network, authentication, authorization, and resource controls that protect a node from untrusted API traffic. An RPC endpoint is an application service, not merely port 8899 opened on a validator: it accepts JSON bodies that can trigger expensive database, history, account-index, block, transaction, and simulation work. Treat it as an internet-facing API with a separate security boundary from consensus.
The Agave RPC-node guide recommends placing public RPC behind a reverse proxy or load balancer. This page explains the operational contract that needs to sit around that advice.
Start with the service boundary
Keep these roles separate whenever the endpoint matters to external users:
| Layer | Owns | Must not own |
|---|---|---|
| Voting validator | Consensus identity, vote account, ledger replay | Public application traffic and user authentication |
| RPC backend | A local Agave RPC interface, indexes, history policy | Internet TLS termination or customer credentials |
| Edge proxy / gateway | TLS, authentication, quotas, body limits, request logs | Validator identity keys or ledger storage |
| Indexer / cache | Product-specific derived data and hot reads | A reason to enable every expensive RPC method |
A small private cluster may deliberately combine roles, but the exception should be documented with its traffic limit, owner, and exit condition. --private-rpc is useful for a validator’s local control plane; it is not a public-service security control.
Exposure model
Expose the proxy, not the validator process. Bind the Agave RPC listener to a private network or host interface when architecture allows it, and permit inbound traffic only from the proxy/load-balancer security group. Keep gossip, TPU/TVU, dynamic UDP ports, metrics, and administrative access on their own documented paths; they are not substitutes for one another.
At the edge, provide:
- TLS termination with a maintained certificate policy.
- An explicit allowlist of public JSON-RPC methods. Start with the product’s required methods instead of forwarding every method by default.
- Authentication for paid, partner, or administration tiers. API keys should identify a client for quotas and auditing; they are not a secret that makes a backend safe by itself.
- Per-key and per-source-IP limits, plus a global concurrency/queue limit to protect the backend when keys are absent or compromised.
- Request-body size, maximum batch size, connection lifetime, and upstream timeout limits.
- A fixed response to malformed, oversized, unauthorized, and rate-limited requests. Do not proxy an arbitrary error page from an upstream host.
Do not publish an administrative endpoint (host SSH, node exporter, Prometheus, Grafana, RPC admin controls, or an unauthenticated load-balancer dashboard) alongside the public RPC hostname.
Method classes and policy
Classify methods by their cost and data sensitivity before assigning a quota. Costs vary with account size, history availability, requested ranges, account indexes, cache warmth, and the exact Agave version. The categories below are a starting point for measurement, not universal limits.
| Class | Typical examples | Default treatment |
|---|---|---|
| Light reads | getHealth, getSlot, getBalance, getLatestBlockhash | Available to standard callers; short cache where semantics permit. |
| Account reads | getAccountInfo, getMultipleAccounts, getProgramAccounts | Limit request size and filters; meter separately from light reads. |
| Transaction submission | sendTransaction, simulateTransaction | Authenticate; constrain payload size and concurrent simulations; report upstream/cluster errors distinctly. |
| Historical scans | getBlock, getTransaction, getSignaturesForAddress | Require history retention; price and limit independently; bound ranges and pagination. |
| Index-dependent queries | getProgramAccounts, token-owner/mint lookups | Enable the matching index only after load testing; cap filters and result work. |
| Administrative/debug | Any method not in the public product contract | Keep private or deny at the proxy. |
Never infer a method’s production cost from a single successful request. Record p50/p95/p99 latency, response bytes, backend CPU, disk read/write latency, cache hit rate if available, error rate, and timeout rate by method class.
JSON-RPC batches, retries, and fan-out
Batches make it easy for one HTTP request to produce many backend operations. Define whether batches are supported and, if so, cap the number of calls and aggregate work. Apply authentication and quota accounting to the actual work performed, not only the number of HTTP connections.
Clients also retry aggressively when an RPC endpoint returns a timeout or a transient gateway error. A retry storm can turn a partial backend impairment into a full outage. Return 429 for quota enforcement, use 503 only for temporary unavailable capacity, include Retry-After where appropriate, and make client examples use bounded exponential backoff with jitter. A request that timed out at the proxy may still be running or may already have been submitted upstream: transaction-submission clients must reconcile using signatures, not blindly resubmit.
History and index controls
--enable-rpc-transaction-history, --full-rpc-api, and --account-index change both the API promise and the operating cost. Before enabling one, write down:
- The exact methods and customer feature it unlocks.
- Retention boundary and behavior when data is absent or pruned.
- Which account index is required:
program-id,spl-token-mint, and/orspl-token-owner. - Measured incremental disk growth, replay/startup time, tail latency, and recovery impact under a representative workload.
- A rollback plan if disk or replay pressure breaches its SLO.
The public RPC node guide maps program-id to getProgramAccounts, spl-token-mint to mint-oriented token lookups, and spl-token-owner to owner-oriented token lookups. Enable only the exact indexes the public contract requires. An indexer can be a better fit for broad analytical queries than turning a consensus-adjacent node into a general database.
Observability and audit trail
Create edge metrics with low-cardinality labels: route/method class, HTTP status family, auth tier, backend pool, and cache outcome. Avoid putting raw API keys, wallet addresses, transaction payloads, or full query strings into Prometheus labels or broadly accessible logs.
For every request, retain a privacy-reviewed correlation ID, timestamp, method class, status, response size, backend latency, and quota result. Define a short protected retention period for investigation, document access to it, and redact secrets before exporting a support bundle.
Alert on sustained backend saturation, rising edge 5xx, queue/concurrency exhaustion, a sudden change in method mix, high 429 rates for a key or source, unusual response sizes, and a divergence between proxy success and backend health. Pair those alerts with the Solana exporter: a responsive proxy is not proof that its backend is caught up to the cluster.
Failure behavior
Decide this before the incident:
- If one backend lags, remove it from the eligible pool using slot/health-aware readiness, not a TCP-only probe.
- If all history-capable backends are unhealthy, fail historical methods clearly rather than silently routing them to nodes that cannot satisfy the contract.
- If a capacity limit is reached, shed the most expensive classes first and preserve essential light reads / authenticated product paths where that matches the published service policy.
- If a public RPC is degraded, do not redirect arbitrary traffic to a voting validator as an emergency shortcut.
Run a controlled test for each case: dead backend, slot-lagged backend, saturated getProgramAccounts, oversized request, bursty batch traffic, and a client retry storm. Verify not only that the gateway responds, but that validator health, disk latency, and peer connectivity remain within normal bounds.
Launch review
Before publishing an endpoint, confirm all of the following:
- Public DNS resolves only to the intended edge layer.
- Direct backend access is blocked from the internet.
- TLS, authentication, quotas, method allowlist, request-size/batch limits, and timeouts have automated tests.
- The advertised methods, retention behavior, commitment behavior, rate limits, and support channel are written down for users.
- A load test of the advertised contract has a recorded baseline and a rollback threshold.
- Dashboard and alerts distinguish edge failure, RPC-backend failure, node lag, and upstream cluster conditions.
- On-call staff can disable a costly method class or key without restarting a validator.