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
- Console
- API
- CLI
- Go
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.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.
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 inload_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:
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.
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.
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 carriesdns_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.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 servetcp and udp on the same port number, but
never two tcp listeners on one.
- Console
- API
- CLI
- Go
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.
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.
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:
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:
{ "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 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.
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, omitdefault_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 includetarget_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.
- Console
- API
- CLI
- Go
Open the listener and choose Add Rule. Evaluation order takes the
Priority, Conditions builds the match, and Forward to picks the
Target group.
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.
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 omitaccount_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.
- Console
- API
- CLI
- Go
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.
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
- Console
- API
- CLI
- Go
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.
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:- Console
- API
- CLI
- Go
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.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.
- source_ip
- none
Seeing the real client
Anhttp 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.
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
- Console
- API
- CLI
- Go
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.
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.- Console
- API
- CLI
- Go
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.
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.- 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_countor 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.
Deleting
- Console
- API
- CLI
- Go
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.
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.
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 load balancer is active but nothing answers on the VIP
The load balancer is active but nothing answers on the VIP
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.The create is refused over the floating IP
The create is refused over the floating IP
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.Targets are stuck at initial health
Targets are stuck at initial health
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.A rule does not match what I expected
A rule does not match what I expected
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.I cannot detach a certificate
I cannot detach a certificate
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.A resize has not finished
A resize has not finished
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.Deleting a target group returns 409
Deleting a target group returns 409
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.

