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

# Floating IPs

> Public or private virtual addresses that move independently of a NIC, with address-targeted attachment and member health.

A floating IP is a separately allocated IPv4 or IPv6 address. Public addresses
come from the regional public pool; private addresses come from a selected
subnet and remain inside its VPC. Both translate to an address on the target NIC,
so attaching or moving one does not reconfigure the guest.

A public floating IP is not reachable merely because you allocated it. Reaching an instance
from the internet takes four things, and three of them have nothing to do with
the address itself.

<Warning>
  **Allocating a floating IP and attaching it is not enough.** The instance's
  subnet also needs an internet gateway attached to the VPC *and* a `0.0.0.0/0`
  route pointing at that gateway. Without the route the reply traffic has
  nowhere to go, and from outside it looks exactly like the inbound packet being
  dropped — so you spend the afternoon debugging the wrong direction.

  The API refuses the attach for this reason rather than handing you a dead
  address.
</Warning>

## The reachability chain

```mermaid theme={null}
flowchart LR
  NET([Internet]) --> IGW["Internet gateway<br/>attached to the VPC"]
  IGW --> RT{"Subnet's route table<br/>0.0.0.0/0 → that gateway?"}
  RT -- no --> D1["Reply has no way out.<br/>Looks like inbound blackhole."]
  RT -- yes --> FIP["Floating IP<br/>attached to the NIC"]
  FIP --> SG{"Security group<br/>allows the port?"}
  SG -- no --> D2["Dropped at the interface"]
  SG -- yes --> VM([Instance])
```

<Steps>
  <Step title="Attach an internet gateway to the VPC">
    <Tabs>
      <Tab title="Console">
        Go to **Networking → Internet Gateways** and choose **Create Internet
        Gateway**. A **Name** is all it takes, and the gateway is "Created
        detached; attach it to a VPC from the list to start routing traffic
        out."

        Open it and choose **Attach**, pick the **VPC**, and confirm with
        **Attach**. **Status** goes from **Detached** to **Attached** and
        **Attached VPC** fills in.
      </Tab>

      <Tab title="API">
        ```bash theme={null}
        POST /v1/internet-gateways        { "name": "main" }
        POST /v1/internet-gateways/{id}/attach  { "vpc": "<vpc>" }
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network internet-gateway create --name main
        basaltic network internet-gateway attach <igw-id> --vpc <vpc-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        n := network.New(cfg)
        g, err := n.CreateInternetGateway(ctx, &network.InternetGatewayCreateRequest{
            Name: "main",
        })
        g, err = n.AttachInternetGateway(ctx, g.ID, &network.InternetGatewayAttachRequest{
            VPC: vpcID,
        })
        ```
      </Tab>
    </Tabs>

    Attaching creates no routes. It only makes the gateway available as a
    route target.
  </Step>

  <Step title="Add the default route to the subnet's route table">
    <Tabs>
      <Tab title="Console">
        Go to **Networking → Route Tables** and open the table the subnet
        uses — the subnet's page names it under **Route table**. Choose **Add
        Route**, set **Destination CIDR** to `0.0.0.0/0`, set **Target type**
        to **Internet Gateway**, pick the gateway under **Internet gateway**,
        and confirm with **Add Route**.

        If the picker is empty and says "No IGW attached to this VPC. Attach
        one first.", you are still on the previous step.
      </Tab>

      <Tab title="API">
        ```bash theme={null}
        POST /v1/route-tables/{route_table_id}/routes
        { "destination_cidr": "0.0.0.0/0", "target_internet_gateway": "<igw>" }
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network route create <route-table-id> \
          --destination-cidr 0.0.0.0/0 --target-internet-gateway <igw-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        r, err := network.New(cfg).CreateRoute(ctx, routeTableID, &network.RouteCreateRequest{
            DestinationCIDR:             "0.0.0.0/0",
            TargetInternetGateway: basaltic.String(gatewayID),
        })
        ```
      </Tab>
    </Tabs>

    This is what makes the subnet public. It is also what makes the *reply*
    path exist.
  </Step>

  <Step title="Attach the floating IP to the interface">
    <Tabs>
      <Tab title="Console">
        Go to **Networking → Floating IPs**, choose **Allocate Floating IP**
        if you do not have a spare one, then open the address and choose
        **Attach**. Pick the NIC under **Interface**; its matching address is selected as the target. Confirm with **Attach
        floating IP**.

        The same attach is on the NIC: open it under **Networking →
        Interfaces** and choose **Attach floating IP**.
      </Tab>

      <Tab title="API">
        ```bash theme={null}
        POST /v1/floating-ips/{floating_ip_id}/attach
        { "interface": "<interface>", "address_id": "<address-id>" }
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network floating-ip attach <floating-ip-id> \
          --interface <interface-id> --address-id <address-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        fip, err := network.New(cfg).AttachFloatingIP(ctx, floatingIPID,
            &network.AttachFloatingIPRequest{Interface: interfaceID, AddressID: addressID})
        ```
      </Tab>
    </Tabs>

    If the subnet has no default route to an internet gateway, this fails
    with `400`: "the interface's subnet has no default route (0.0.0.0/0) to an
    internet gateway — attach an internet gateway to the VPC and add a default
    route first". The console shows that same text under **Failed to attach
    floating IP**.
  </Step>

  <Step title="Allow the traffic in a security group">
    <Tabs>
      <Tab title="Console">
        Open the NIC under **Networking → Interfaces** and choose **Attach
        security group**. Its rules "start filtering this interface's traffic
        as soon as it is attached."
      </Tab>

      <Tab title="API">
        ```bash theme={null}
        PUT /v1/interfaces/{interface_id}/security-groups
        { "security_groups": ["<sg>"] }
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network interface set-security-group <interface-id> \
          --security-groups <sg-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        groups, err := network.New(cfg).SetInterfaceSecurityGroups(ctx, interfaceID,
            &network.InterfaceSecurityGroupsRequest{
                SecurityGroups: []string{securityGroupID},
            })
        ```
      </Tab>
    </Tabs>

    An interface in no security group drops everything.
    [Security groups](/networking/security-groups) covers the default posture.
  </Step>
</Steps>

<Tip>
  **Create VPC** in the console does the first two steps for you. Turn on
  **Internet gateway** under **Internet access** and ask for at least one
  public subnet, and the workflow runs **Create internet gateway**, **Attach
  internet gateway** and **Configure public internet route** as part of
  provisioning. The floating IP and the security group are still yours to do
  afterwards.
</Tip>

<Note>
  The guard runs in both directions. Deleting the `0.0.0.0/0` route while
  floating IPs still depend on it is refused too: "cannot delete the default
  route: *N* floating IP(s) in this route table's subnets depend on it for
  internet reachability — detach them first". Any other route deletes normally,
  and so does the default route once nothing is attached.
</Note>

## Allocating an address

Choose `visibility` (`public` by default or `private`) and `family` (`ipv4`
by default or `ipv6`) at allocation. These values are immutable. The address
is assigned by the platform. A private allocation also requires `subnet`.

<Tabs>
  <Tab title="Console">
    Go to **Networking → Floating IPs** and choose **Allocate Floating IP**.
    Choose **Visibility** and **Family**, and select a subnet for a
    private allocation. Add a **Description** and **Tags** if desired.
    Confirm with **Allocate IP**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/floating-ips
    { "description": "web front door" }          # IPv4 (the default)
    { "description": "web front door", "family": "ipv6" }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic network floating-ip create --description "web front door"
    basaltic network floating-ip create --description "web front door" --family ipv6
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    fip, err := network.New(cfg).CreateFloatingIP(ctx, &network.FloatingIPCreateRequest{
        Description: basaltic.String("web front door"),
        Family:      basaltic.Ptr(network.IPFamilyIPv6), // omit for IPv4
    })
    ```
  </Tab>
</Tabs>

An IPv4 address counts against your `floating_ips_v4` quota, the same one a NAT
gateway address draws on; an IPv6 address counts against `floating_ips_v6`. The
two are separate allowances — a v4 address is a share of the region's scarce
IPv4 block, a v6 one is not.

## Public and private attachment

Read the target NIC's `addresses` array and pass its matching-family child ID
as `address_id` alongside `interface`. A NIC can have one public and one
private floating IP per family: at most four mappings with the current address
limits. A public and private IPv4 FIP can both translate to the same guest IPv4
address; the same is true for IPv6.

Public IPv4 requires `0.0.0.0/0` to an internet gateway. Public IPv6 requires
`::/0` to an internet gateway and a directly attached IPv6 address. NAT66 maps
the FIP to the NIC's primary IPv6 `/128`, whether that address is global or ULA.
New outbound connections from that target address use the public FIP while
attached. If the target has a global IPv6 address, both that native address and
the floating IP remain reachable, subject to the subnet's routes and security
group rules. Replies use the address the client connected to. Attaching or
detaching the FIP requires no guest address changes.
Other global addresses within the NIC's `/96` keep their native routing.
Untranslated ULA traffic cannot leave for the public internet.

Private FIPs need no internet gateway. Their allocation subnet and target NIC
must be in the same VPC; they may be in different subnets. The address remains
reserved in its allocation subnet until released, including while detached.
Private mappings translate incoming traffic and its replies, without replacing
the NIC's source address for unrelated outbound connections. They are useful
for private service identities and custom load balancers.

Detach removes the mapping but keeps the allocation. It does not allocate a
replacement public address. The guest address remains unchanged. Without a
public FIP, IPv4 can use a NAT gateway, while a native global IPv6 address can
use its internet route directly.

Health checks apply to private as well as public NIC-backed FIPs. Shared
multi-member addresses remain managed through instance pools; the manual
interface attachment endpoint takes one member.

<AccordionGroup>
  <Accordion title="Attach" icon="link">
    <Tabs>
      <Tab title="Console">
        Open an unattached address and choose **Attach**, then pick the NIC under **Interface** and
        confirm with **Attach floating IP**. The same action is on the NIC
        itself, as **Attach floating IP** under **Networking → Interfaces**.
      </Tab>

      <Tab title="API">
        `POST /v1/floating-ips/{floating_ip_id}/attach` with `interface` (an interface UUID or nested CRN) and `address_id` (the matching NIC address child ID).
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network floating-ip attach <floating-ip-id> \
          --interface <interface-id> --address-id <address-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        fip, err := network.New(cfg).AttachFloatingIP(ctx, floatingIPID,
            &network.AttachFloatingIPRequest{Interface: interfaceID, AddressID: addressID})
        ```
      </Tab>
    </Tabs>

    Re-attaching to a NIC that already holds it is a no-op success.

    It is refused when:

    * a public FIP lacks a default internet-gateway route in its family (`400`);
    * a private FIP and target NIC belong to different VPCs (`400`);
    * the target address is absent or belongs to the other family (`400`);
    * the interface already carries a different floating IP in the same
      family and visibility (`409`);
    * the address already has a member, so a second one would make it an
      anycast address (`409`, see below);
    * the address is bound to a resource that owns its own bindings, such as a
      load balancer or an instance pool — attach and detach it there. The
      console does not offer **Attach** on such an address at all: its
      **Attached interface** reads **Bound by another service**, or **No
      replica is serving it yet** for a pool.
  </Accordion>

  <Accordion title="Detach" icon="unlink">
    <Tabs>
      <Tab title="Console">
        Choose **Detach** on the address's page, or **Detach floating IP**
        from the NIC's page. Confirm with **Detach**. Pool and load balancer
        bindings must be managed through their owners.
      </Tab>

      <Tab title="API">
        `POST /v1/floating-ips/{floating_ip_id}/detach`. The body is optional:
        an ordinary floating IP has at most one member. Omit `interface` to
        clear the binding, or name its interface by UUID or nested CRN.
        Bare names, null and empty references are rejected.
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network floating-ip detach <floating-ip-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        fip, err := network.New(cfg).DetachFloatingIP(ctx, floatingIPID,
            &network.DetachFloatingIPRequest{})
        ```
      </Tab>
    </Tabs>

    It is idempotent. Detaching an already-detached address, or naming a NIC
    that is not a member, returns `200` with the row unchanged.
  </Accordion>

  <Accordion title="Release" icon="trash-2">
    <Tabs>
      <Tab title="Console">
        **Release floating IP**, on the address's **Settings** tab.
      </Tab>

      <Tab title="API">
        `DELETE /v1/floating-ips/{floating_ip_id}`
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        basaltic network floating-ip delete <floating-ip-id>
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        err := network.New(cfg).DeleteFloatingIP(ctx, floatingIPID)
        ```
      </Tab>
    </Tabs>

    Either way the address goes back to the pool. Refused with `409` while it
    is still attached, or while it belongs to an instance pool. Detach first.
  </Accordion>
</AccordionGroup>

## Reading the bindings

`attached_to` identifies the attachment owner by its canonical CRN. It is
`null` only when the address is unattached. An interface binding names the
nested interface CRN; a pool or load balancer binding names that resource.
An empty pool still owns its address: `members: []` does not mean it is free
to attach or release. Manage pool bindings through the
[pool endpoints](/compute/instance-pools#one-address-for-the-whole-pool).

`members` describes the current bindings, not ownership. Each member has
`interface`, `address_id`, `health`, `reason` and `created_at`. The embedded `interface`
contains `id`, `crn` and a nullable `instance` summary (`id`, `crn`, `name`).
An interface without an owning instance has `instance: null`. Load balancer
members have `interface: null`; use the top-level owner CRN to identify the
load balancer. Check for null before following either summary.

These excerpts from `GET /v1/floating-ips/{floating_ip_id}` illustrate the
attachment fields inside `floating_ip`; other fields are omitted.

An ordinary interface binding:

```json theme={null}
{
  "attached_to": "crn:network:sa-saopaulo-1:my-account:vpc/prod/subnet/public/interface/eth0",
  "members": [{
    "interface": {
      "id": "b9e4c7a2-1f8d-4a3b-9c6e-2d5a8b1f4c7e",
      "crn": "crn:network:sa-saopaulo-1:my-account:vpc/prod/subnet/public/interface/eth0",
      "instance": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "crn": "crn:compute:sa-saopaulo-1:my-account:instance/web",
        "name": "web"
      }
    },
    "health": "unknown",
    "reason": "unprobed",
    "created_at": "2026-09-01T12:00:00Z"
  }]
}
```

A pool with no current members retains its ownership:

```json theme={null}
{
  "attached_to": "crn:compute:sa-saopaulo-1:my-account:instance-pool/web",
  "members": []
}
```

As replicas join, the pool's `members` contains the same embedded interface
and instance summaries as an ordinary binding. The owner remains the pool CRN.

An unattached address:

```json theme={null}
{"attached_to": null, "members": []}
```

A load balancer binding:

```json theme={null}
{
  "attached_to": "crn:loadbalancer:sa-saopaulo-1:my-account:load-balancer/web",
  "members": [{
    "interface": null,
    "health": "healthy",
    "reason": "passing",
    "created_at": "2026-09-01T12:00:00Z"
  }]
}
```

### Filter by attachment owner

Pass an exact canonical CRN to `attached_to` on `GET /v1/floating-ips`.
For example, this lists a pool's addresses even when the pool has no members:

```http theme={null}
GET /v1/floating-ips?attached_to=crn:compute:sa-saopaulo-1:my-account:instance-pool/web&limit=50
```

The filter is applied before pagination and intersected with other filters.
While `meta.has_more` is true, pass `meta.marker` as `marker` on the next
request, preserving `attached_to` and the other filters. A malformed CRN,
an empty value, or a flat interface CRN returns `400`. A well-formed CRN for
another account, region, or unsupported resource type returns an empty page.
The filter does not accept UUIDs, bare names, or `null` to select free addresses;
list addresses and select those whose `attached_to` is null instead.

With the released CLI, use the canonical singular command and `--all` to
walk every matching page:

```bash theme={null}
basaltic network floating-ip list --attached-to 'crn:compute:sa-saopaulo-1:my-account:instance-pool/web' --all
```

With the Go SDK, pass the same filter to `ListFloatingIPs`:

```go theme={null}
page, err := network.New(cfg).ListFloatingIPs(ctx, &network.ListFloatingIPsParams{
    AttachedTo: "crn:compute:sa-saopaulo-1:my-account:instance-pool/web",
})
```

This returns one page. Preserve `AttachedTo` when setting `Marker` for the
next page; alternatively, the SDK's `ListFloatingIPsAll` iterator walks all
pages for you.

The SDK represents JSON `attached_to: null` as an empty `AttachedTo` string.

### Understand member health

| Health      | Meaning                                                                                    | Receives traffic? |
| ----------- | ------------------------------------------------------------------------------------------ | ----------------- |
| `unknown`   | No check is running; ordinary hand-attached members without a health check use this state. | Yes               |
| `healthy`   | The guest is up and any configured readiness check passes.                                 | Yes               |
| `unhealthy` | Waiting for guest liveness, or a configured readiness check fails.                         | No                |

`reason` explains the state: `unprobed` means no check, `booting` means the
guest has not yet been reached, `probe_failed` means a configured check is
failing, and `passing` means the guest is up and any configured check passes.
An unhealthy member remains in `members`; membership alone is not readiness.
Without `health_check`, healthy indicates guest liveness, not application
readiness. See [health troubleshooting](/networking/troubleshooting#attachment-and-health-fields).

## One address in front of several instances

An address with more than one member is an anycast address, and only an
[instance pool](/compute/instance-pools) can own one. Attaching a second interface by hand is
refused with `409`.

A pool keeps membership in step as it scales. Every live replica can join,
including replicas sharing a host; placement affects capacity and resilience,
not whether a member can receive traffic. Members still booting can appear
in the list as unhealthy before they receive traffic.

<Tabs>
  <Tab title="Console">
    Open the pool under **Compute → Instance pools**, go to its **Floating
    IPs** tab and choose **Attach floating IP**. Detaching is the row's
    **Detach floating IP** action on that same tab.

    The address's own page will not let you do this — an address a pool holds
    offers no **Attach** button at all, and its **Attached interface** reads
    **No replica is serving it yet** until a replica picks it up. Use
    **Attachment owner** to open the owning pool or load balancer; if the
    owner cannot be resolved, its CRN remains visible. The **Interfaces** tab
    shows embedded member identities, **Health** and **Reason**, without manual
    member controls. An empty pool remains attached.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST   /v1/instance-pools/{pool_id}/floating-ips
    { "floating_ip": "<floating-ip-id-or-crn>" }
    DELETE /v1/instance-pools/{pool_id}/floating-ips/{floating_ip_id}
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic compute instance-pool attach-floating-ip <pool-id> \
      --floating-ip <floating-ip-id>
    basaltic compute instance-pool detach-floating-ip <pool-id> <floating-ip-id>
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    c := compute.New(cfg)
    fip, err := c.AttachInstancePoolFloatingIP(ctx, poolID,
        &compute.InstancePoolFloatingIPAttachRequest{FloatingIP: floatingIPID})
    err = c.DetachInstancePoolFloatingIP(ctx, poolID, floatingIPID)
    ```
  </Tab>
</Tabs>

With several healthy members, new connections are distributed among them.
Private pool addresses stay inside their VPC. Public pool addresses require
internet routing in their selected family. Connections already established
with a member do not migrate when that member disappears.

<Warning>
  A shared address does not terminate TLS or route HTTP requests. Without a
  configured `health_check`, member health indicates guest liveness only.
  Connections in flight to a member that goes away end rather than move.
</Warning>
