> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novacula.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pools

> Serve several nodes behind one domain and one TLS certificate

In high-load production systems, **redundancy** is what keeps the service up: when one node stops serving, another is already there to take over. A **pool** brings that redundancy to your RPC.

A pool puts several nodes behind one domain and one TLS certificate. Callers reach the domain; each request is answered by a node its executor last saw running, probing OK, and reporting itself synced. A node that stops meeting that bar is dropped from rotation, and so is one you take out for maintenance — the domain your clients call stays the same.

Each executor in a pool runs a gateway for the pool's traffic. A request lands on one executor — by DNS round-robin, or the client's own choice — and that executor answers from its **own node** whenever that node qualifies; if it doesn't, the request is forwarded to another executor in the pool. So **nodes buy redundancy and executors buy capacity**: a second node on the same host stands by rather than sharing the load.

<Note>
  Failover covers a member that refuses the request — a closed port, an unreachable host, a certificate the caller can't verify. A member that accepts a request and then goes quiet ends that one call with a `502` and is **not** retried elsewhere: the request may already have executed, and re-sending it to another node would run it twice. A pool masks a dead node, not a wedged one.
</Note>

Open **Pools** in the sidebar. A pool is fixed to one **chain/network** (set by its first node) and one **domain** (set when you create it) — to serve a different pair or domain, create another pool.

<Note>
  A pool needs at least one executor with a **public IPv4 address**, and every pool executor must accept **inbound TCP 443** — that's where the pool's endpoint terminates TLS. Set `advertised_host` in the executor's config (`[agent]` in `/etc/nvcl/agent.toml`, `[operator]` on Kubernetes) and restart it. It has to be a public IPv4 **literal**: peers dial the address exactly as advertised, so a DNS name doesn't work. A node whose executor advertises nothing usable is **withheld** — the pool keeps serving through its other nodes.
</Note>

## Create a pool

**Create pool** opens a four-step wizard:

<Steps>
  <Step title="Domain">
    Choose the pool domain — the exact hostname clients will call (for example `rpc.example.org`), hostname only, no scheme, path, or port. **It can't be changed after the pool is created.** Creating the pool immediately generates a private key and CSR for this exact domain; the private key stays in the Hub. To serve a different domain, create another pool.
  </Step>

  <Step title="Nodes">
    Select 1–100 nodes. **The first node fixes the pool's chain and network** — every later node must match. Only compatible, unpooled nodes are offered.
  </Step>

  <Step title="Review">
    Confirm the domain, the pool's chain/network, and the initial members before anything is saved.
  </Step>

  <Step title="Setup">
    Publish the DNS records shown, then send the CSR to your certificate authority. Uploading the signed certificate here is optional — you can install it later from the **Certificate** tab. Until it's installed, each executor serves only its own node: a call to another executor has no certificate to verify, so there is no failover between hosts yet.
  </Step>
</Steps>

Creating the pool locks in its domain and generates its private key before any node is added. If adding the nodes fails, the pool itself is kept — correct the selection and retry just that step.

Once created, the pool has five tabs: **Overview**, **Members**, **Certificate**, **DNS**, and **Activity**.

## Overview

The **Overview** tab is the pool at a glance — its **client endpoint** (the domain callers use), its chain/network, and a **setup status** with a readiness checklist over **Domain**, **TLS certificate**, **DNS records**, and **Members**.

The status describes the **setup, not live traffic**:

| Status          | Meaning                                                                      |
| --------------- | ---------------------------------------------------------------------------- |
| **Draft**       | Not serving yet — setup is incomplete.                                       |
| **Not serving** | Something is blocking the pool from serving.                                 |
| **At risk**     | Serving, but something needs attention (e.g. a certificate about to expire). |
| **Ready**       | Nothing known is blocking the pool.                                          |

## Members

The **Members** tab lists the pool's nodes — each row shows the **node**, its **executor** (name and public IP), and whether it's **in rotation**:

* **In rotation** — nothing in the pool's setup keeps this node out. Whether it serves a given request is decided by its executor on every check-in, from the node's own state, so a node that is stopped or still syncing still reads *in rotation* here.
* **Withheld** — its executor advertises no address, a name instead of an address, or an address that isn't a public IPv4, so callers have no way to reach it; the pool serves through its other nodes. Fix by advertising a public IPv4 on that executor.
* **Cordoned** — the pool skips this node while it's cordoned, and the node keeps running. Cordoning isn't settable from the console yet.

A node qualifies on what its executor observed at its last check-in: the process running, the probe answering, and the client reporting itself synced. That last one is the client's own sync flag, not a block-lag measurement — a node that reports synced while trailing the chain head keeps serving.

Add nodes by dragging one from **Compatible nodes** onto the list, or with **Add nodes** (added all-at-once, up to 100). Only nodes on the pool's chain/network that aren't already pooled are offered.

A pool member needs no public port of its own — the pool's gateway reaches it over loopback on an Agent, and through the in-cluster Service on an Operator. Set a member's [RPC exposure](/docs/nodes/rpc-exposure) to **Local**, and the pool domain becomes the only way in. Joining a pool doesn't close the port for you: a member still published on its own is flagged **also public** in this list.

On Kubernetes that's more than hygiene. A member in **Direct** mode is published as a NodePort or LoadBalancer instead of through the Service the pool's gateway dials, so another pod in the same cluster can't fail over to it — the same goes for **Public domain** with RPC-key auth, since the gateway carries no key. **Local** is the mode that keeps failover working inside a cluster.

## Certificate

The pool's private key and CSR are generated for its domain when you create it; the private key stays in the Hub. On the **Certificate** tab (also the create wizard's **Setup** step):

1. **Get the CSR signed** — **Copy CSR** or **Download CSR** and send it to your certificate authority.
2. **Install the signed chain** — paste the signed chain (starting with `-----BEGIN CERTIFICATE-----`) or **Choose File**, then **Save certificate**.

The tab shows the certificate state — **Not issued yet**, **Valid until \<date>**, **Expires in N days**, or **Expired**. **Renewing is the same step:** install a fresh chain before the current one expires.

## DNS

The **DNS** tab lists the records to create at your DNS provider — an **A record** per executor public IPv4 that answers for the pool, so the domain round-robins across your nodes' hosts. Each row shows the **type**, **name**, **value** (the executor's public IPv4), and which **executor** and **nodes** it covers. Novacula can't check your DNS zone, so verify the records at your provider.

## Activity

The **Activity** tab logs what's happened to the pool — membership changes, certificate installs, and the like. A brand-new pool reads *Nothing has happened to this pool yet.*

## The pool endpoint

Clients call `https://<pool-domain>`. That endpoint is **open**: unlike a node's own [Public domain](/docs/nodes/rpc-exposure#public-domain) exposure, it takes no RPC key and applies no rate limit in this release. The only calls it refuses are the chain's node-control methods (Bitcoin's `stop` and `setban`, and their equivalents elsewhere). Restrict the domain at your own edge if that matters.

What it serves is narrower than a node's own endpoint:

|                    | Pool endpoint                                                                                | Node's own endpoint  |
| ------------------ | -------------------------------------------------------------------------------------------- | -------------------- |
| **Protocol**       | HTTP JSON-RPC only — a WebSocket upgrade is refused with `426`                               | HTTP and WebSocket   |
| **Request body**   | up to 2 MiB, then `413`                                                                      | the node's own limit |
| **Time to answer** | a member that hasn't answered in \~4 s counts as failed, and one request gets \~8 s in total | 60 s                 |

Send heavy calls — a wide `eth_getLogs`, a `debug_trace*` — to a node directly: through a pool they can come back as a `502` while the same call succeeds against the node itself.

## Permissions

* **View** pools — any organization member.
* **Create and delete** pools, add or remove members, and install certificates — **Owners** and **Admins**.

A pool's domain, chain, and network can't be changed after creation, by any role. See [Roles and permissions](/docs/platform/roles-and-permissions).

## Related

* [RPC exposure](/docs/nodes/rpc-exposure) — how a single node exposes RPC.
* [Deploy a node](/docs/nodes/deploy-a-node)
* [Connect an executor](/docs/executors/connect-an-executor)
