Skip to main content
Client examples use CLI v0.27.0 and Go SDK v0.32.0. See CLI setup and Go configuration. Go snippets assume a configured cfg, a ctx from context.Background(), and imports for log, basaltic (github.com/basaltic-sh/sdk-go) and loadbalancer (github.com/basaltic-sh/sdk-go/loadbalancer). LOAD_BALANCER_ID, LISTENER_ID and TARGET_GROUP_ID (and their Go equivalents) are returned UUIDs. See Resource references for accepted reference kinds, lookup scopes, canonical identities and exact list filters. See Resource faults for the active faults array. 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 — across your selected VPC subnets, so it reaches your backends over private addresses. A public floating IP provides its internet-facing address. Replica native IPv6 addresses cannot accept new connections from outside the VPC; use the load balancer addresses governed by exposure. The service is regional: https://loadbalancer.sa-saopaulo-1.basaltic.sh.

application

Layer 7. Hosts http and https listeners, routes on host, path, header, query and method, and terminates TLS.

network

Layer 4. Hosts tcp and udp listeners and forwards streams to a single target group.
type is fixed at create. There is no patch that converts one into the other — create a second load balancer and move the address.

Create one

The subnets, the flavor family, and the security group requirement that trips people up.

Give it an address

The private VIP, the floating IP you can only choose at create, and the hostname you point a name at.

Listeners and rules

Protocols, exposure, certificates, and how a request picks a target group.

Target groups

Targets, health checks, stickiness, and PROXY protocol.

Replicas and resizing

Scaling, the surge-first flavor roll, and how to watch it.

Troubleshooting

Active but unreachable, targets stuck at initial, a resize that stalls.

Creating a load balancer

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, one or more Subnets for instance placement, and the Security groups every replica inherits. Under IP addresses, choose the private and optional public floating IP for each enabled family. Private addresses default to Automatic — allocate from first subnet; public addresses default to None. Pick a Flavor — the list is already filtered to the load balancer family — then set Minimum count, Maximum count and Desired count under Scale. Enable Automatic scaling to configure metric targets.
Address choices are fixed at creation. A load balancer without a public floating IP stays internal — see how it gets its address.
Each floating IP list offers only eligible addresses that are not attached to anything. Allocate one under Networking → Floating IPs first if it is empty.
Relationship inputs accept UUIDs, CRNs or exact immutable names. The VPC scopes subnet names; the referenced resources must belong to your account in the same region. Flavors come from the regional catalog. Floating IPs accept UUIDs or CRNs only. Load balancer, listener, rule, target group, and target lists accept exact name and crn filters; an empty filter selects no rows, and malformed CRNs return a validation error.
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.
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.
must belong to vpc
Select one or more distinct subnets in the same VPC, account, and region. Each instance gets one interface. New instances use the least-populated eligible subnet during creation, scaling, and replacement. Automatic private floating IPs are allocated from the first subnet.
1–10
Use 1 ≤ min_count ≤ desired_count ≤ max_count ≤ 10. Desired defaults to 1; omitted bounds default to desired. Pick a minimum of at least 2 for HA. Leave room below the maximum for a flavor replacement. See scaling.
replica_count remains a deprecated alias for desired_count. If both are supplied, they must agree. The CLI and Go SDK support independent bounds and autoscaling policies. Keep the maximum above the desired count to allow replacement capacity during a flavor roll. 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.
The replicas run the platform’s own proxy software. You reach a load balancer over its address; there is no tenant SSH access to its replicas.

Reading placement back

The response wraps one resource in load_balancer. Its subnets array and flavor contain ResourceRefs with id, crn, and an optional name. The response does not include account_id. This excerpt shows the relationship fields:
Read each subnet separately for its VPC, address ranges, and route table. The console links every subnet and uses the first subnet to resolve the VPC link. Create takes vpc and flavor as string selectors, and subnets as an array of string selectors. Placement is immutable: PATCH /v1/load-balancers/{id} accepts neither vpc nor subnets. To reuse a response reference in a request, pass its id or crn.

How a load balancer gets its address

A load balancer uses floating IPs for both private and public addresses. At creation, floating_ips can select one address per family and visibility: private IPv4, public IPv4, private IPv6, and public IPv6. Selected private addresses can belong to any selected subnet. A missing private family is allocated automatically from the first subnet when enabled there. Public addresses are optional and must be selected explicitly.
Use existing, free floating IPs in the same account and region. Each item in floating_ips has id, crn, visibility, and address, the same compact reference used by network interfaces. Infer IPv4 or IPv6 from address and use visibility to distinguish private and public addresses. There are no separate floating_ip_id, internal_ipv4, internal_ipv6, or public_ipv6 fields. These floating IPs forward listener traffic to healthy replicas; they are not extra addresses configured inside the guests. In the console, use the IP addresses card on Create Load Balancer. Each private selection defaults to Automatic — allocate from first subnet, and each public selection defaults to None. The available choices follow the selected subnets’ enabled families and routes. Automatically allocated private floating IPs belong to the load balancer, are covered by its quota, and are released when it is deleted. Existing addresses you supplied are detached and retained when it is deleted. Neither kind can be detached independently while the load balancer owns it.
Address selection is fixed at creation. PATCH /v1/load-balancers/{id} does not change it. To add public exposure or replace an address, create a new load balancer with the required floating IPs and move traffic to it.
Public IPv4 requires 0.0.0.0/0 through an internet gateway in the subnet’s route table; public IPv6 requires ::/0 through an internet gateway and an IPv6-enabled subnet. NAT and egress-only gateways do not provide public listener exposure. A private ULA subnet can use a public IPv6 floating IP: NAT66 translates it to the replica addresses. Select addresses only through the floating_ips array at creation.

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.
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.
The load balancer name is fixed after creation, so updates keep its hostname. 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.

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.
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.
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 selects which of the load balancer’s floating-IP addresses forward traffic to the listener. The selection applies to both IPv4 and IPv6.
private floating IPs
Forwarded through the load balancer’s private addresses only.
public floating IPs
Forwarded through the load balancer’s public addresses only.
default when a floating IP is attached
Forwarded through both the private and public addresses.
Security groups must also allow the listener port. Allowing a port in a security group does not make it available through an LB address excluded by exposure. On subnets with routed GUA IPv6, managed replicas also have native NIC addresses. New connections from outside the VPC to those native addresses are blocked, including when a security group permits the port. Public clients must use a public load balancer address. Replica-initiated connections and their replies still work, as does traffic inside the VPC. This policy applies to both public and internal load balancers; ordinary instance NICs keep native IPv6 reachability. 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 public floating IP, private_only when it does not. Asking for public_only or both on a load balancer with no public floating IP is rejected. The console disables both public options in that case and says Public exposure unavailable.
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.

Certificates on an HTTPS listener

An HTTPS listener needs at least one certificate. On creation, certificates is an array of UUID, CRN, or name strings; the first becomes the default. No key material is sent to this API: replicas fetch it from the certificate service under their own identity. Listener responses expose default_certificate and default_target_group as ResourceRefs with id, crn, and optional name, or null when absent. They do not include load_balancer_id or the full certificate collection. A listener matches the client’s SNI against the attached certificates’ SANs and uses default_certificate when none matches or the client sends no SNI. List the attached certificates through their own collection:
This read requires loadbalancer:ListListenerCertificates on the listener. It returns the same public certificate objects as GET /v1/certificates, including domains, status, expiry, challenges, faults, and public PEMs. Private keys are excluded. No certificate-service permission is required. Compare each id with listener.default_certificate.id to identify the default. Attach a certificate with a UUID, CRN, or name:
The response is { "certificate": ResourceRef }. Setting is_default: true selects the attached certificate as the default. To switch to a certificate already attached to this listener, use:
The new default and the previous default change in one transaction.
The console selects certificates while you create the listener, then shows them read-only with a default badge. Use the API to attach, detach, or change the default on an existing listener.
You cannot detach the last certificate from an HTTPS listener, or detach its current default while other certificates remain. Select a replacement default first.
Detach with the certificate UUID returned by the collection, not an attachment UUID or a certificate CRN:
Renewed certificates are picked up automatically. Use default_certificate to choose the default from the certificates already attached to the listener. Attachment changes also update the replicas’ access to the currently attached certificates.

Default target group

On PATCH, omit default_target_group to keep the current default. Send a UUID, CRN or exact account-scoped name to replace it, or default_target_group: null to clear it. default_target_group 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 default_target_group: null once your rules cover everything you serve.

Routing rules

Rule responses include target_group as a ResourceRef with id, crn, and optional name. They omit listener_id; the listener is already identified by the request path. Lists return { "rules": [...], "meta": { "limit": 1, "has_more": false } }, with limit equal to the number of returned 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.
Open the listener and choose Add Rule. Evaluation order takes the Priority, Conditions builds the match, and Forward to picks the Target group.
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. The console lists the operators as equals, prefix, glob and regex — only exact reads differently there.
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.
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 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.
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.

Target groups

Target-group responses omit account_id. Pool-backed groups include instance_pool as a ResourceRef with id, crn, and optional name, replacing instance_pool_id. Static groups omit instance_pool. Create requests continue to accept an instance_pool UUID, CRN, or name string. 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.
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.
http | https | tcp | udp
Must match the protocol of the listener that points at it.
ip | instance (default ip)
What target means on each attached target.
static | pool (default static)
static uses the targets you attach. pool takes its backends from a compute instance pool named by instance_pool, 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.
1–65535
The default port for targets in the group. A target can override it.
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.
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.

Attaching targets

Open the target group and choose Attach Target. The first field follows the group’s target type — IP address or Instances — 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.
target is a literal IP address on an ip group and a compute instance UUID, CRN or name 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. Target responses use instance as a ResourceRef (id, crn, and optional name) for instance targets, including targets derived from an instance pool. IP targets use an address string instead. Exactly one of these fields is present; target_group_id and target_ref are omitted. The target’s own id identifies its registration for get and detach operations. Lists return targets plus meta with limit and has_more: false. A target’s optional port overrides the target group’s backend port. For example, a group using port 80 can include a target on 8080. When port is omitted, the target inherits the group’s current port; pool-derived targets always inherit it. Attach requests still use the target string selector. 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.
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.
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:
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.
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.
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.
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.
Read the result on each target:

Session affinity

By default every request is balanced independently. Turn on stickiness per target group:
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.
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

desired_count is the target number of replicas. min_count and max_count bound both manual and automatic scaling. One replica is enough to work; two or more can keep serving if one is lost.
These are managed instances: compute’s instance endpoints also return them, while instance mutations remain restricted to the load balancer service.
The response contains instances and pagination meta. Each entry uses the same contract as a compute pool instance: id, crn, name, lifecycle fields, and flavor, image and iam_role ResourceRefs. The stable sequence number is in metadata["basalt:pool:sequence_num"]. Filter by name, crn, current_state, flavor or image, and page through results with limit and marker. Load balancer instances add proxy_ok, optional last_seen and agent_version, and optional retirement details. Their status describes proxy health, while current_state describes the instance lifecycle: 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

Scale on the load balancer opens Scale load balancer. It shows the Current size and takes Minimum count, Maximum count and Desired count. Enable Automatic scaling to configure CPU or custom metric targets, then choose Scale to save. The Scaling tab shows the policy and recent decisions.
The CLI and Go calls above set the bounds and desired capacity together. The maximum of six leaves replacement capacity above the four desired replicas. A bounds-only update clamps the current desired count into the new range; an explicit desired count outside that range is rejected. Scale-out provisions replicas from the load balancer’s managed instance pool. Scale-in withdraws retiring replicas from new traffic, waits for the proxy to acknowledge draining, then allows the configured grace period before deletion. The default grace period is 120 seconds. Long-lived TCP, UDP and WebSocket sessions can end when a replica is removed; plan for reconnects. Changes to floating IP forwarding membership can also change connection placement during withdrawal. The autoscaling field accepts the same CPU and custom metric policies as instance pools. For example, CPU target tracking at 60% adjusts desired capacity between your minimum and maximum. Custom metrics can use queue depth, request rates or other demand published to Telemetry. Load balancers always retain at least one replica. Missing or stale samples prevent scale-in. Configure the load balancer’s policy through its own API. Its managed pool cannot be resized independently. Backend instance pools have their own bounds and policies: changing proxy capacity does not resize your application workers. Load balancer telemetry includes instance_id on each replica’s series. For counters, calculate rates per series before summing across replicas so that a restart or replacement cannot mask another replica’s traffic. Sum each replica’s latest active-connection gauge for a total. Target-health gauges describe each replica’s view of the same backends, so keep those views separate or use a minimum or maximum; summing them counts backends repeatedly. For latency histograms, calculate counter rates per series and combine matching bucket boundaries across replicas before computing a percentile.

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.
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.
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 roll waits for replacement capacity before retiring old capacity. The extra replica must fit inside max_count. Desired and minimum remain unchanged; rollout_surge indicates the temporary extra member. When the last old replica goes, the pool returns to desired capacity.
Watch it on GET /v1/load-balancers/{id}/instances. A replica has been replaced when its id changes, and the resize is done when every flavor.id there matches the load balancer’s flavor.id. Seeing one more replica listed than desired_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 Instances tab marks a replica the roll has not reached yet, and the load balancer’s page counts how many are on the new size.
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 at its maximum has no replacement headroom. Raise max_count or reduce desired capacity before resizing. A flavor change is refused when there is no room for a replacement. A roll already in progress waits if a later bounds change removes that room. Autoscaling pauses during the roll.
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

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.
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

A fresh heartbeat resolves only the liveness-owned codes (REPLICAS_DEGRADED, REPLICAS_UNAVAILABLE). It leaves a provisioning or render fault standing.

Limits and naming

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. Load-balancer and target-group names are fixed after creation because IAM policies address their CRNs. Updates containing name return a validation error, including unchanged, empty or null values. Existing resources retain their current names. Deleting a resource and reusing its name creates a different resource, even if its CRN is the same.
name-based
Listeners and rules have no separate names. Their CRNs use UUID components under the immutable load balancer name: load-balancer/<name>/listener/<uuid> and load-balancer/<name>/listener/<uuid>/rule/<uuid>. Pass a UUID or URL-encoded CRN in their path parameters; bare listener and rule names are not accepted.A load balancer’s CRN ends in load-balancer/<name> and a target group’s in target-group/<name>. Because the CRN carries the name, an IAM policy can wildcard a naming convention rather than listing ids. Take the exact string from the resource’s crn field rather than assembling it.
1–10
Both at create and on a patch.
1–50000
Unique per listener.
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

The most common cause is that no security group on the replicas opens the listener port. security_groups 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.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.
A selected 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, and the whole create fails rather than leaving you a load balancer without the address you asked for. Attach an internet gateway to the VPC and add the default route, then create it again — floating_ips is a create-time field only, so there is no second chance at it afterwards.
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.
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, or gets a 503 reading no default target group if there is none.
Two removals are refused: the last certificate on an HTTPS listener, and the certificate identified by default_certificate 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.
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}/instances 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.
A listener’s default_target_group or a rule’s target_group still points at it. Repoint or delete the reference, then delete the group.

Next

Certificates

Issuing the certificates a listener serves, and how renewal reaches the load balancer.

Networking

VPCs, subnets, security groups, internet gateways and floating IPs.

Compute

Instances and instance pools — the backends a target group points at.

DNS

Pointing your own domain at a load balancer.