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

# Load balancers

> Managed load balancers: listeners, routing rules, target groups, health checks, and the replicas that serve your traffic.

A load balancer accepts connections on one or more listeners and forwards them
to a target group. It runs on compute instances the platform operates for you —
the **replicas** — inside your own VPC subnet, so it reaches your backends over
private addresses and is reachable from the internet only if you give it a
floating IP.

The service is **regional**: `https://loadbalancer.sa-saopaulo-1.basaltic.sh`.

<Columns cols={2}>
  <Card title="application" icon="globe">
    Layer 7. Hosts `http` and `https` listeners, routes on host, path, header,
    query and method, and terminates TLS.
  </Card>

  <Card title="network" icon="cable">
    Layer 4. Hosts `tcp` and `udp` listeners and forwards streams to a single
    target group.
  </Card>
</Columns>

`type` is fixed at create. There is no patch that converts one into the other —
create a second load balancer and move the address.

<CardGroup cols={2}>
  <Card title="Create one" icon="plus" href="#creating-a-load-balancer">
    The subnet, the flavor family, and the security group requirement that
    trips people up.
  </Card>

  <Card title="Give it an address" icon="map-pin" href="#how-a-load-balancer-gets-its-address">
    The private VIP, the floating IP you can only attach at create, and the
    hostname you point a name at.
  </Card>

  <Card title="Listeners and rules" icon="route" href="#listeners">
    Protocols, exposure, certificates, and how a request picks a target group.
  </Card>

  <Card title="Target groups" icon="target" href="#target-groups">
    Targets, health checks, stickiness, and PROXY protocol.
  </Card>

  <Card title="Replicas and resizing" icon="layers" href="#replicas">
    Scaling, the surge-first flavor roll, and how to watch it.
  </Card>

  <Card title="Troubleshooting" icon="life-buoy" href="#troubleshooting">
    Active but unreachable, targets stuck at `initial`, a resize that stalls.
  </Card>
</CardGroup>

## Creating a load balancer

<Tabs>
  <Tab title="Console">
    Go to **Compute → Load Balancers** and choose **Create Load Balancer**.

    Under **Load balancer details**, give it a **Name** and pick the **Type**:
    **Application** for layer 7, **Network** for layer 4. **Placement** takes
    the **VPC**, the **Subnet** the virtual IP is reserved from, and the
    **Security groups** every replica inherits. Pick a **Flavor** — the list
    is already filtered to the load balancer family — then set **Replica
    count** under **Scale**.

    <Warning>
      **The console cannot create an internet-facing load balancer.** The form
      has no floating IP field, and the address is accepted only at create, so
      what you get here is reachable from inside the VPC and nowhere else. Use
      the API if it needs a public address.
    </Warning>
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST https://loadbalancer.sa-saopaulo-1.basaltic.sh/v1/load-balancers
    {
      "name": "web-lb",
      "type": "application",
      "vpc_id": "c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9",
      "subnet_id": "d4e5f6a7-b8c9-4012-d3e4-f5a6b7c8d9e0",
      "flavor_id": "e5f6a7b8-c9d0-4123-e4f5-a6b7c8d9e0f1",
      "security_group_ids": ["d1b6f3a8-4c2e-4a9d-8f7b-1e5c3a2d9b4f"],
      "replica_count": 2
    }
    ```
  </Tab>
</Tabs>

<ParamField body="security_group_ids" type="required, at least one">
  The replicas inherit these on every NIC, and a NIC in no security group
  accepts nothing. A load balancer without one would still provision, still
  take a floating IP and still report `active` — while answering no one. The
  create is refused instead. **The listener port has to be opened by a security
  group listed here**, or the load balancer is unreachable on it.
</ParamField>

<ParamField body="flavor_id" type="loadbalancer-family only">
  The replicas are platform-operated and priced accordingly, so a general or
  database flavor is rejected with the flavor's family named in the error.
</ParamField>

<ParamField body="subnet_id" type="must belong to vpc_id">
  The replicas get NICs here and the virtual IP is reserved out of this
  subnet's range.
</ParamField>

<ParamField body="replica_count" type="1–10, default 1">
  Pick at least 2 for HA. See [replicas](#replicas).
</ParamField>

The response is **`201`** with the load balancer in `provisioning`. It flips to
`active` on the first replica whose proxy reports ready — the replicas boot,
install their software and pull their configuration, so expect a few minutes.

<Note>
  `key_names` is not a tenant feature. The replicas run the platform's own
  proxy software, and SSH into them is reserved for operator break-glass; the
  field is refused for any other account. You reach a load balancer over its
  address, never over SSH.
</Note>

## How a load balancer gets its address

Every load balancer holds a **private virtual IP** (`vip_v4`) reserved from its
subnet at create. That address is what internal clients in the VPC use, and it
is the only address a load balancer has unless you give it one more.

For public traffic you attach a **floating IP**:

```json theme={null}
{ "...": "...", "floating_ip_id": "f6a7b8c9-d0e1-4234-f5a6-b7c8d9e0f1a2" }
```

<Note>
  This is **API only**. **Create Load Balancer** in the console has no floating
  IP field, and the load balancer's detail page shows **Public IP** as a
  read-only fact rather than something you can set. Combined with the field
  being create-time, that means a public load balancer has to be created
  through the API.
</Note>

<Warning>
  `floating_ip_id` is accepted **only on create**. `PATCH /v1/load-balancers/{id}`
  takes `name`, `replica_count`, `flavor_id` and `tags` — not an address. A
  load balancer created without one cannot be made internet-facing later;
  create a new one with the floating IP and move traffic to it.
</Warning>

<Warning>
  The attach is refused unless the load balancer's subnet already routes
  `0.0.0.0/0` to an internet gateway. Reply traffic leaves by that subnet's
  route table, so without the default route the address would be handed back
  unreachable. Add the gateway and the route first — see
  [networking](/networking).
</Warning>

On an internet-facing load balancer in a dual-stack subnet you also get
`public_vip_v6`, a public IPv6 address. IPv6 has no NAT, so that address *is*
the public ingress rather than a translation of something else. It is allocated
best-effort at create: a single-stack subnet simply leaves the load balancer
public over IPv4 only. The `vip_v6` field is reserved and is not populated
today.

### Pointing a name at it

The response carries `dns_name`, a hostname published for you in a shared
regional zone, shaped `{name}.{account}.lb.<region>.<base-domain>`. It resolves
to the floating IP on an internet-facing load balancer and to the private VIP
otherwise.

<Note>
  Read `dns_name` from the response rather than assembling it. It is empty in a
  region where the convenience zone is not configured — the VIP and floating IP
  stay authoritative either way.
</Note>

Renaming the load balancer republishes the record under the new name and
retires the old one, so anything pointing at the old hostname stops resolving.
For your own domain, publish a `CNAME` to `dns_name` — or an `A` record to the
floating IP if you need an apex — with [DNS](/dns).

## Listeners

A listener binds one protocol and port on the load balancer. The pair has to be
unique — a second listener on the same protocol and port is refused — so a
network load balancer can serve `tcp` and `udp` on the same port number, but
never two `tcp` listeners on one.

<Tabs>
  <Tab title="Console">
    Open the load balancer and choose **Add Listener**. The **Listener** card
    takes the **Protocol**, the **Port** and the **Exposure**; picking HTTP or
    HTTPS moves the port to 80 or 443 for you. **Routing** sets the
    **Default target group**.

    An HTTPS listener grows a **TLS certificates** card. Pick one or more
    under **Certificates** — the console notes that the first one you select
    becomes the default and the rest are SNI options.

    The **Protocol** list only ever offers what this load balancer's type
    accepts, so the mismatch described below cannot be made here.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/load-balancers/{id}/listeners
    { "protocol": "https", "port": 443,
      "certificates": [{ "certificate_crn": "crn:certificate::my-account:certificate/prod-web" }],
      "default_target_group_id": "b8c9d0e1-f2a3-4456-b7c8-d9e0f1a2b3c4" }
    ```
  </Tab>
</Tabs>

| Load balancer `type` | Accepted `protocol` |
| -------------------- | ------------------- |
| `application`        | `http`, `https`     |
| `network`            | `tcp`, `udp`        |

Mixing them is rejected at create with the reason named. Protocol values are
lowercase, which is the platform convention for enums we define; values that
come from a standard keep that standard's own casing — an HTTP method in a rule
condition is `GET`, not `get`.

### Exposure

`exposure` picks which of the load balancer's addresses the listener binds.

<ResponseField name="private_only" type="the subnet VIP">
  Reachable from inside the VPC only.
</ResponseField>

<ResponseField name="public_only" type="the floating IP">
  Reachable from the internet only.
</ResponseField>

<ResponseField name="both" type="default when a floating IP is attached">
  Binds the VIP and the floating IP.
</ResponseField>

The console spells the same three choices **Private only (VPC-internal VIP)**,
**Public + private** and **Public only (floating IP)** on the **Exposure**
field of **Add Listener**.

The default adapts to the load balancer: `both` when it carries a floating IP,
`private_only` when it does not, because with no public address there is no
public traffic for a listener to see. Asking for `public_only` or `both` on a
load balancer with no floating IP is rejected up front rather than accepted and
silently kept private. The console disables both public options in that case
and says **Public exposure unavailable**.

<Warning>
  A `public_only` listener stops being served entirely if the floating IP goes
  away — it has no address left to bind. A `both` listener keeps serving
  privately. Patch `exposure` to `private_only` if you meant to keep it
  internal.
</Warning>

### Certificates on an HTTPS listener

An HTTPS listener needs at least one certificate, named **by CRN**. No key
material is ever sent to this API: the listener stores a reference, and the
replicas fetch the material from the [certificate service](/certificates) under
their own identity.

A listener can hold several certificates and picks one per connection by
matching the client's SNI against each certificate's SANs. The one flagged
`is_default` is the fallback for a client whose SNI matches nothing, or that
sends none at all.

```bash theme={null}
POST /v1/load-balancers/{id}/listeners/{listener_id}/certificates
{ "certificate_crn": "crn:certificate::my-account:certificate/prod-web",
  "is_default": true }
```

Setting a new default demotes the previous one in the same transaction, so a
listener always has exactly one.

<Note>
  Attaching and detaching on a **live** listener is **API only**. The console
  chooses certificates once, while you are adding the listener; afterwards the
  listener's page shows them read-only, marking the default with a `default`
  badge. Changing the set — or promoting a different default — goes through
  these two calls.
</Note>

<Warning>
  Detaching is refused in two cases: removing the **last** certificate from an
  HTTPS listener, and removing the **current default** while other certificates
  are still attached. Promote a replacement first, then detach.
</Warning>

<Note>
  A certificate CRN ends in `certificate/<name>`, so the slash must be
  percent-encoded as `%2F` when the CRN sits in a path segment. Sent raw it
  addresses a different route that does not exist.

  ```bash theme={null}
  DELETE /v1/load-balancers/{id}/listeners/{listener_id}/certificates/crn:certificate::my-account:certificate%2Fprod-web
  ```
</Note>

Certificates the platform renews are picked up on their own — a reissue changes
the fingerprint the replicas track and they re-fetch. To force a re-check of an
already-attached certificate, patch the listener with its CRN. Attaching and
detaching also re-scope the replicas' access so it covers exactly the
certificates currently attached, and nothing else.

### Default target group

`default_target_group_id` is where a request goes when no rule matches. On a
`tcp` or `udp` listener it is the **only** destination — rules do not apply at
layer 4 — so an L4 listener without one has nowhere to send traffic.

An HTTP or HTTPS listener with no default and no matching rule answers **`503`**
with the body `no default target group`. Set it, or clear it deliberately with
`clear_default_target_group: true` once your rules cover everything you serve.

## Routing rules

Rules exist on `http` and `https` listeners only; creating one on an L4
listener is refused. Each rule has a priority, a list of conditions, and a
target group.

<Tabs>
  <Tab title="Console">
    Open the listener and choose **Add Rule**. **Evaluation order** takes the
    **Priority**, **Conditions** builds the match, and **Forward to** picks the
    **Target group**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/load-balancers/{id}/listeners/{listener_id}/rules
    {
      "priority": 100,
      "conditions": [
        { "field": "host", "op": "exact",  "values": ["api.example.com"] },
        { "field": "path", "op": "prefix", "values": ["/v1"] }
      ],
      "target_group_id": "b8c9d0e1-f2a3-4456-b7c8-d9e0f1a2b3c4"
    }
    ```
  </Tab>
</Tabs>

Rules are evaluated in ascending priority and the **first match wins**;
`priority` is `1..50000` and unique per listener. Every condition on a rule has
to match — the list is an AND.

| `field`  | Matches                                            | In the console  |
| -------- | -------------------------------------------------- | --------------- |
| `host`   | The request's authority (its `Host`)               | **Host header** |
| `path`   | The request path                                   | **Path**        |
| `header` | The header named in `name`                         | **HTTP header** |
| `query`  | The query-string key named in `name`               | **Query param** |
| `method` | The HTTP method, spelled as HTTP spells it — `GET` | **HTTP method** |

| `op`     | Behaviour                                                    |
| -------- | ------------------------------------------------------------ |
| `exact`  | Whole-value equality                                         |
| `prefix` | Value starts with                                            |
| `glob`   | Only `*` is a wildcard; every other metacharacter is literal |
| `regex`  | RE2 syntax                                                   |

The console lists the operators as **equals**, **prefix**, **glob** and
**regex** — only `exact` reads differently there.

<Warning>
  Give each condition a **single value**. `values` is an array, but the data
  plane matches on the first entry and ignores the rest. Express alternatives
  with `glob` or `regex`, or write one rule per value.
</Warning>

A `header` or `query` condition without `name` is rejected, and so is a `regex`
value that does not compile as RE2. That strictness is deliberate: a load
balancer's configuration is built in one pass, so a single untranslatable
condition would stop **every** replica loading any configuration at all —
including replacements a resize is waiting on. Refusing the write costs you one
error instead of an outage.

Updating a rule is a full replace: send `priority`, `conditions` and
`target_group_id` together, the same shape as create. The console does the same
thing behind **Edit Rule**, titled with the rule's priority — the form comes up
populated and **Save Rule** writes the whole rule back.

<Note>
  Delete a rule through its listener —
  `DELETE /v1/load-balancers/{id}/listeners/{listener_id}/rules/{rule_id}`. The
  listener-less form still works for clients already on it, but it has to scan
  the load balancer's listeners to prove the rule belongs there.
</Note>

## Target groups

A target group is the named set of backends a listener or rule forwards to. It
is an account-scoped resource of its own, not a child of a load balancer, so one
group can back several listeners — which is what makes a blue/green swap a
matter of repointing a rule.

<Tabs>
  <Tab title="Console">
    Go to **Compute → Target Groups** and choose **Create Target Group**. The
    console files target groups under **Compute** even though they belong to
    the load balancer API.

    **Target group** takes the **Name**, the **Protocol** and the **Port**.
    **Backends** picks the **Backend mode** — **Static targets** for a set you
    attach yourself, **Instance pool** to track a pool — and, for a static
    group, the **Target type**: **IP address** or **Instance**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/target-groups
    { "name": "web-targets", "protocol": "http", "target_type": "instance", "port": 8080 }
    ```
  </Tab>
</Tabs>

<ResponseField name="protocol" type="http | https | tcp | udp">
  Must match the protocol of the listener that points at it.
</ResponseField>

<ResponseField name="target_type" type="ip | instance (default ip)">
  What `target_ref` means on each attached target.
</ResponseField>

<ResponseField name="target_mode" type="static | pool (default static)">
  `static` uses the targets you attach. `pool` takes its backends from a
  compute instance pool named by `instance_pool_id`, so scaling the pool moves
  the backend set with it — and attaching a target by hand is refused with a
  `409`, because a row nothing would ever route to is worse than an error. A
  pool-mode group is forced to `target_type: instance`.
</ResponseField>

<ResponseField name="port" type="1–65535">
  The default port for targets in the group. A target can override it.
</ResponseField>

Deleting a group while a listener default or a rule still references it is a
**`409`** — repoint or delete the reference first. In the console that is
**Delete target group**, on the group's **Settings** tab. The number of listeners per
load balancer, target groups per load balancer and targets per group are account
quotas; exceeding one is refused at the write.

<Note>
  `target_type: function` is accepted on a group, but attaching a target to it
  is refused: the function runtime is not available yet, so the group has no way
  to resolve a backend.
</Note>

### Attaching targets

<Tabs>
  <Tab title="Console">
    Open the target group and choose **Attach Target**. The first field
    follows the group's target type — **IP address** or **Instance ID** — and
    **Port** defaults to the target group port.

    A pool-mode group has no **Attach Target** button at all: membership
    tracks the instance pool, so there is nothing to attach by hand.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/target-groups/{id}/targets
    { "target_ref": "10.0.1.20", "port": 8080 }
    ```
  </Tab>
</Tabs>

`target_ref` is an IP address on an `ip` group and a compute instance id on an
`instance` group. An instance reference stays unresolved in the row and becomes
an address at configuration time, so an instance whose NIC address changes needs
no update here. The instance has to exist in your account —
see [compute](/compute).

Addresses are stored canonically, so the spelling you read back may differ from
the one you sent, and two spellings of the same endpoint are recognised as the
duplicate they are.

<Warning>
  An `ip` target has to be a **routable unicast address**. Loopback,
  link-local, multicast and unspecified addresses are rejected. This is a
  security boundary, not tidiness: link-local carries the instance metadata
  endpoint and loopback is the replica's own administrative socket. Both are
  reachable from a replica, so without the check a listener would proxy them
  straight out to the internet. The IPv4-mapped IPv6 form is rejected too —
  send the dotted IPv4 form.
</Warning>

Detaching a target removes it immediately — **Detach** on the target's row in
the console, confirmed as **Detach target**.

### Health checks

The data plane probes each target and reports what it sees. Set the knobs per
group, at create or with a patch:

<Tabs>
  <Tab title="Console">
    **Create Target Group** exposes exactly one of these, as **Health check
    path** under **Health & connection**. The field only appears for an HTTP
    or HTTPS group.

    <Note>
      The interval, timeout and thresholds are **API only**. A target group's
      **Health check** tab displays whatever they are set to, but nothing in
      the console writes them — send the block below to change one.
    </Note>
  </Tab>

  <Tab title="API">
    ```json theme={null}
    {
      "health_check": {
        "protocol": "http",
        "path": "/healthz",
        "interval_sec": 30,
        "timeout_sec": 5,
        "healthy_threshold": 3,
        "unhealthy_threshold": 3
      }
    }
    ```
  </Tab>
</Tabs>

`protocol` defaults to the group's own. Probing over HTTP on a `tcp` group is
supported and common — a backend that speaks a binary protocol can still serve
a health page. `path` defaults to `/` for `http` and `https`; `tcp` and `udp`
checks are connect-only and ignore it. The thresholds are consecutive results:
three failures in a row to go unhealthy, three successes to come back.

<Note>
  `health_check.matcher` and `health_check.port` are accepted and stored, but
  are **not applied to the probe** today. A probe hits the target's own port.
  Leave them unset rather than expecting them to change anything.
</Note>

Read the result on each target:

| `health`    | Meaning                                                                              |
| ----------- | ------------------------------------------------------------------------------------ |
| `initial`   | No successful probe yet. A target that never leaves this state is not being reached. |
| `healthy`   | Passing, and receiving traffic.                                                      |
| `unhealthy` | Failing, and taken out of rotation.                                                  |

### Session affinity

By default every request is balanced independently. Turn on stickiness per
target group:

<Note>
  In the console this is the **Stickiness** field — on **Create Target Group**,
  and on an existing group's **Settings** tab. It offers **None**, **Cookie**
  and **Source IP**, with **Cookie name** and **Duration (seconds)** appearing
  under **Cookie**. **Cookie** is only listed for an HTTP or HTTPS group, for
  the reason below.
</Note>

<Tabs>
  <Tab title="cookie">
    ```json theme={null}
    { "session_affinity": { "type": "cookie", "cookie_name": "BASALTICLB", "duration_sec": 86400 } }
    ```

    The load balancer sets an opaque cookie on the first response and sends
    every later request carrying it to the same backend. `http` and `https`
    groups only — there is no cookie on a raw stream. `cookie_name` defaults to
    `BASALTICLB` and `duration_sec` to one day, up to a ceiling of seven days
    (`604800`).

    The cookie is a random value that means nothing off this load balancer. It
    does not encode which backend was chosen, so a client cannot read your
    internal addresses out of it.
  </Tab>

  <Tab title="source_ip">
    ```json theme={null}
    { "session_affinity": { "type": "source_ip" } }
    ```

    Hashes the client address. Works on every protocol and is the only option
    for `tcp` and `udp`. Be aware that a NAT gateway in front of your clients
    makes every client behind it one key, which concentrates them on one
    backend.
  </Tab>

  <Tab title="none">
    ```json theme={null}
    { "session_affinity": { "type": "none" } }
    ```

    The default. Turning stickiness **off** on an existing group needs this
    explicit body — omitting `session_affinity` from a patch leaves the current
    setting alone.
  </Tab>
</Tabs>

Both modes hash consistently, so adding or losing a backend moves only the
clients that backend was serving instead of reshuffling everyone.

### Seeing the real client

An `http` or `https` target group already gets the client's address in
`X-Forwarded-For`, and any `X-Forwarded-For` the client sent itself is not
trusted — the load balancer is the edge, so nothing upstream of it counts.

For `tcp` and `udp` groups, or backends that prefer a framed envelope, set
`proxy_protocol: true` and upstream connections are wrapped in a PROXY v2
header carrying the original client address and port. The console switch is
**PROXY protocol (v2)**, under **Health & connection**.

## Replicas

`replica_count` is how many instances serve your traffic. One is enough to
work; two or more is what survives losing one.

```bash theme={null}
GET /v1/load-balancers/{id}/replicas
```

<Note>
  Replicas are managed instances, so `GET /v1/instances` does not return them by
  design. This endpoint is the only window onto them.
</Note>

Each entry carries `instance_id`, `replica_index`, the `flavor_id` it actually
booted on, and a liveness view refreshed on every health report:

| `status`       | Meaning                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------- |
| `initializing` | The replica has never reported — boot still in flight. `last_seen` is absent.            |
| `healthy`      | Reporting, and its proxy is serving.                                                     |
| `unhealthy`    | Reporting, but its proxy is down. It is taken out of the traffic path until it recovers. |

The load balancer itself goes `active` on the first replica to report healthy,
and drops to `error` only when **every** replica has gone silent past the
staleness window — one missed report is not enough. It returns to `active` as
soon as any replica resumes.

### Scaling

<Tabs>
  <Tab title="Console">
    **Scale** on the load balancer opens **Scale load balancer**. It shows the
    **Current size** and takes a **Desired count**; **Scale** applies it.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    PATCH /v1/load-balancers/{id}
    { "replica_count": 4 }
    ```
  </Tab>
</Tabs>

Scale-out provisions the new replicas; scale-in removes the highest-indexed
ones. Range is `1..10`.

### Changing the flavor

A running instance cannot change size in place, so a resize records the new
size and returns — the replicas already up are replaced one at a time in the
background, over the following minutes.

<Tabs>
  <Tab title="Console">
    **Resize** on the load balancer opens **Resize load balancer**. Pick the
    new **Flavor** and confirm with **Resize**. The button is disabled while a
    resize is already running — the console will not stack two rolls.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    PATCH /v1/load-balancers/{id}
    { "flavor_id": "e5f6a7b8-c9d0-4123-e4f5-a6b7c8d9e0f1" }
    ```
  </Tab>
</Tabs>

**The pool grows before it shrinks.** An extra replica comes up on the new
flavor and starts serving *before* any replica on the old one is retired, so
the number serving never drops below `replica_count` — including at
`replica_count: 1`, where retiring first would mean serving nothing while the
replacement booted. When the last old replica goes, the pool comes back down.

<Info>
  Watch it on `GET /v1/load-balancers/{id}/replicas`. A replica has been
  replaced when its `instance_id` changes, and the resize is done when every
  `flavor_id` there matches the load balancer's. Seeing one more replica listed
  than `replica_count` part-way through is the surge keeping your capacity up,
  not a replica leaking — it goes away when the last old one does.

  The console reads the same thing for you: the **Replicas** tab marks a
  replica the roll has not reached yet, and the load balancer's page counts how
  many are on the new size.
</Info>

Two things worth knowing:

* **Nothing is retired until everything is healthy.** The roll waits for the
  pool to be complete with every replica reporting and its proxy up. A
  replacement that never comes up healthy stalls the resize with the load
  balancer whole, rather than walking it down one replica per pass.
* **A load balancer already at 10 replicas has nowhere to grow.** There the
  replicas are replaced in place and nine serve while each replacement boots.

A resize is rejected up front if your account lacks the compute quota for the
replacement replica, so it cannot half-apply and leave the load balancer short.

## Deleting

<Tabs>
  <Tab title="Console">
    On the load balancer's **Settings** tab, **Delete load balancer**. You are
    asked to type the load balancer's name to confirm.

    A listener is deleted from the **Listeners** tab instead, and takes its
    rules with it. Target groups survive both — they are account-scoped
    resources, not children of the load balancer.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    DELETE /v1/load-balancers/{id}
    ```
  </Tab>
</Tabs>

Answers **`202`**. The load balancer moves to `deleting` and stays readable
while its address reservation, replicas and internal state are released, with
the record removed last. Poll until it answers `404` rather than treating the
`202` as proof it is gone. Repeating the delete is safe.

## Statuses

```mermaid theme={null}
stateDiagram-v2
    [*] --> provisioning: create
    provisioning --> active: a replica reports its proxy healthy
    active --> error: every replica silent past the staleness window
    error --> active: any replica resumes reporting
    active --> deleting: delete
    error --> deleting: delete
    deleting --> [*]: teardown converges
```

| Status         | Meaning                                                                                               |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| `provisioning` | Replicas are booting and installing their software. Nothing has reported yet.                         |
| `active`       | At least one replica is serving.                                                                      |
| `error`        | Every replica has gone silent. The instances may still be up; `error_message` says what was observed. |
| `deleting`     | Teardown in flight. Still readable until the record is removed.                                       |

## Limits and naming

<ResponseField name="name" type="unique per account">
  Starts with a letter, then letters, digits, `.`, `_` or `-`, up to 127
  characters. It appears in the CRN, so it has to be URL-safe.
</ResponseField>

<ResponseField name="crn" type="name-based">
  A load balancer's CRN ends in `loadbalancer/<name>` and a target group's in
  `target-group/<name>`. Because the CRN carries the name, an
  [IAM policy](/iam/policies) can wildcard a naming convention rather than
  listing ids. Take the exact string from the resource's `crn` field rather than
  assembling it.
</ResponseField>

<ResponseField name="replica_count" type="1–10">
  Both at create and on a patch.
</ResponseField>

<ResponseField name="priority" type="1–50000">
  Unique per listener.
</ResponseField>

Listeners per load balancer, target groups per load balancer and targets per
target group are account quotas rather than fixed numbers. List operations page
with `limit` and `marker`; page until `meta.has_more` is false rather than until
a page looks short. Creates accept an `Idempotency-Key` header, which makes a
retry return the original outcome instead of a duplicate.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The load balancer is active but nothing answers on the VIP" icon="triangle-alert">
    The most common cause is that no security group on the replicas opens the
    listener port. `security_group_ids` is set at create and cannot be patched
    on the load balancer — change the rules inside the security groups you
    already attached, or recreate with the right set. See
    [networking](/networking).

    The second cause is a listener whose `exposure` is `private_only` when you
    expected public, or `public_only` on a load balancer whose floating IP is
    gone — a `public_only` listener with no public address is not served at all.
  </Accordion>

  <Accordion title="Attaching the floating IP is refused" icon="globe">
    The load balancer's subnet has no `0.0.0.0/0` route to an internet gateway.
    Reply traffic leaves by that route table, so the address would be
    unreachable. Attach an internet gateway to the VPC and add the default
    route, then create the load balancer with `floating_ip_id` — remember it is
    a create-time field only.
  </Accordion>

  <Accordion title="Targets are stuck at initial health" icon="circle-dashed">
    `initial` means no probe has succeeded yet. Check, in order: the backend is
    listening on the group's `port` (or the target's override); the backend's
    own security group allows the replicas' subnet; and, for an HTTP check, that
    `path` returns a success status. Remember the probe hits the target's own
    port — `health_check.port` is not applied today.
  </Accordion>

  <Accordion title="A rule does not match what I expected" icon="route">
    Three things to check. Rules run in ascending `priority` and the first match
    wins, so a broad low-numbered rule shadows the specific ones under it. Every
    condition on a rule must match — the list is an AND, not an OR. And a
    condition matches on the **first** entry of `values`; extra entries are
    ignored, so express alternatives with `glob` or `regex`.

    With no rule matching, the request goes to the listener's
    `default_target_group_id`, or gets a `503` reading `no default target group`
    if there is none.
  </Accordion>

  <Accordion title="I cannot detach a certificate" icon="shield">
    Two removals are refused: the last certificate on an HTTPS listener, and the
    certificate currently flagged `is_default` while others remain. Attach or
    promote a replacement as the default first — that demotes the old one in the
    same transaction — then detach.

    If the request 404s or errors on the path itself, the CRN's slash was sent
    raw. Percent-encode it as `%2F`.
  </Accordion>

  <Accordion title="A resize has not finished" icon="clock">
    The roll advances by one replica at a time and will not retire anything
    while any replica is unhealthy or still coming up. So a stalled resize
    usually means a replacement that never came up healthy — check
    `GET /v1/load-balancers/{id}/replicas` for one sitting at `initializing` or
    `unhealthy`. The load balancer keeps serving on the replicas it has while
    this is true, which is the point.

    Seeing one replica more than `replica_count` mid-roll is expected.
  </Accordion>

  <Accordion title="Deleting a target group returns 409" icon="link">
    A listener's `default_target_group_id` or a rule's `target_group_id` still
    points at it. Repoint or delete the reference, then delete the group.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Certificates" icon="badge-check" href="/certificates">
    Issuing the certificates a listener serves, and how renewal reaches the
    load balancer.
  </Card>

  <Card title="Networking" icon="network" href="/networking">
    VPCs, subnets, security groups, internet gateways and floating IPs.
  </Card>

  <Card title="Compute" icon="server" href="/compute">
    Instances and instance pools — the backends a target group points at.
  </Card>

  <Card title="DNS" icon="globe" href="/dns">
    Pointing your own domain at a load balancer.
  </Card>
</CardGroup>
