In-house BNB Smart Chain RPC — cheaper than paid providers, keyed, rate-limited, and actually monitored.
Internal RPC
paid BSC providers off the hot path
Per-app keys
401 / 429 at the gateway
~1.5TB
official PBSS snapshot
Out-of-sync alerts
snap-sync aware, not false pages
I deployed and stabilized a BNB Smart Chain full node for internal RPC. Web3 backends used to call paid public RPC — rate limits, per-request cost, noisy-neighbor latency. The node is the in-house eth/net/web3 endpoint those services talk to.
This was not a one-shot compose up. The box was about 1.7 million blocks behind and lying about being synced. I restored an official ~1.5TB PBSS pruneancient snapshot, fixed Geth’s ancient-path semantics, survived a 6.9TB disk at 100%, put a keyed rate-limited gateway in front of unauthenticated Geth HTTP, and rewrote alerts so snap sync did not page as an outage — and a node that actually fell off tip did.
Backends depended on third-party BSC RPC. Cost and limits scaled with traffic. A self-hosted node existed but was stale, OOM’d, and paging falsely. Geth HTTP has no API keys — a naked port would let one app starve the node.
Treat it as production storage and protocol ops, not a Compose file. Confirm sync with the right RPC. Restore the official PBSS snapshot. Cap history with BNB’s prune flags. Keep Geth on loopback. Put a gateway in front with per-app keys and per-key limits. Alert on real lag vs chain tip, not snap-sync artifacts.
Host
Bare metal, Ubuntu, 64GB RAM, 6.9TB RAID
Client
BSC Geth, snap sync, PBSS path scheme, pruneancient so ancient history does not grow without bound
RPC
Geth HTTP only on loopback — eth, net, web3 for internal consumers
Gateway
Reverse proxy with a key per service, per-key rate + burst, 401 / 429 before traffic hits Geth
P2P
Host networking for the chain’s peer port
Alerts
Check eth_syncing first; page when the node is out of sync with public tip after it should be following
rpc.txt
Web3 app → RPC gateway (API key + rate limit) → BSC Geth (localhost)
│
├── key per service / team
├── req/s + burst per key
└── 401 / 429 instead of melting the node
Alerts: eth_syncing first
→ syncing → “sync in progress (current/highest)” — not an outage
→ synced → compare to public tip; page if actually out of syncBefore
After
| Problem | Cause | Fix |
|---|---|---|
| Sync stalled | Swap / OOM | Lower cache and maxpeers; snapshot if the pivot is dead |
| Ancient path error | Geth appends /chain | --datadir.ancient is the parent of chain/ |
| Disk full (~4TB ancient) | History not pruned | --pruneancient (~90k blocks), not --history.blocks |
| False out-of-sync pages | eth_blockNumber is 0 in snap sync | Check eth_syncing first |
| Real lag missed | Alerts treated all lag the same | Once synced, page when behind public tip |
| Node overloaded by one app | Unauthenticated shared RPC | API key per consumer + per-key 429 |
| Clean-exit restart loop | Disk full or stale trie.journal | Free disk; delete stale journals — don’t blindly wipe chaindata |
Have a project in mind?
Let's build something together.