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

# Hybrid: PrivateLink to the control plane

> Reach the xpander deployment manager over AWS PrivateLink instead of the public internet.

Your cluster must reach the xpander **deployment manager**. Two options:

| Option | URL | Security |
| - | - | - |
| **Public (TLS)** | `https://deployment-manager.xpander.ai` | Encrypted over public internet. No setup needed. The installer's default |
| **Private (PrivateLink)** | `https://deployment-manager-privatelink.xpander.ai` | Encrypted over AWS private backbone. This page |

Public needs only outbound HTTPS (via a NAT Gateway in private subnets). If traffic must stay in the AWS network, set up PrivateLink and pass `--privatelink` to the [installer](https://docs.xpander.ai/self-hosted/hybrid-install.md) (or pick **AWS PrivateLink** in the wizard). `--privatelink`, `--staging` and `--dm-url` are mutually exclusive: pass one, or none for public.

## How PrivateLink works

xpander exposes the deployment manager as a **VPC Endpoint Service**. You create a **VPC Endpoint** in your VPC, which gets a private `10.x.x.x` IP. Pods connect to it over the AWS backbone, never the internet.

| Property | Value |
| - | - |
| Service Name | `com.amazonaws.vpce.us-west-2.vpce-svc-0101884b32f655197` |
| Service Region | `us-west-2` |
| Supported Regions | `us-west-2`, `us-west-1`, `eu-west-2`, `eu-central-1`, `ap-south-1` |
| Acceptance Required | No (auto-accepted for allowed accounts) |
| Private DNS Name | `deployment-manager-privatelink.xpander.ai` |

> [!WARNING]
>
> Contact xpander to have your AWS account ID added as an allowed principal before creating the VPC endpoint.

## Set it up

**1. Create the VPC endpoint**

> [!WARNING]
>
> The xpander endpoint service is in `us-west-2`. If your cluster is in a different region, you **must** include the `--service-region us-west-2` flag: this is the most common mistake (see [Troubleshooting](https://docs.xpander.ai/self-hosted/hybrid-troubleshooting.md#privatelink)).

```bash theme={"dark"}
aws ec2 create-vpc-endpoint \
  --vpc-id <VPC_ID> \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.vpce.us-west-2.vpce-svc-0101884b32f655197 \
  --service-region us-west-2 \
  --subnet-ids <PRIVATE_SUBNET_A_ID> <PRIVATE_SUBNET_B_ID> \
  --no-private-dns-enabled \
  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=xpander-privatelink}]' \
  --region <REGION> --profile <PROFILE>
```

Save from the output: `VpcEndpointId`, `Groups[0].GroupId` (the security group), `DnsEntries[0].DnsName`, and `DnsEntries[0].HostedZoneId`.

> [!TIP]
>
> If your cluster is in `us-west-2` (same region as the service), you can omit `--service-region`.

**2. Allow HTTPS on the endpoint security group**

The VPC endpoint is created with the VPC's default security group, which only allows inbound traffic from itself. Allow HTTPS from your VPC CIDR:

```bash theme={"dark"}
aws ec2 authorize-security-group-ingress \
  --group-id <ENDPOINT_SG_ID> \
  --protocol tcp \
  --port 443 \
  --cidr <VPC_CIDR> \
  --region <REGION> --profile <PROFILE>
```

**3. Wait for the endpoint to become available**

The endpoint transitions from `pending` to `available` in 1-3 minutes (auto-accepted):

```bash theme={"dark"}
aws ec2 describe-vpc-endpoints \
  --vpc-endpoint-ids <VPCE_ID> \
  --query 'VpcEndpoints[0].State' \
  --region <REGION> --profile <PROFILE>
```

**4. Create private DNS**

Create a private hosted zone so `deployment-manager-privatelink.xpander.ai` resolves to the endpoint ENI inside your VPC:

```bash theme={"dark"}
aws route53 create-hosted-zone \
  --name deployment-manager-privatelink.xpander.ai \
  --caller-reference "xpander-privatelink-$(date +%s)" \
  --vpc VPCRegion=<REGION>,VPCId=<VPC_ID> \
  --hosted-zone-config PrivateZone=true \
  --profile <PROFILE>
```

Save the `HostedZone.Id` from the output, then create the alias record:

```bash theme={"dark"}
cat <<EOF > /tmp/privatelink-dns.json
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "deployment-manager-privatelink.xpander.ai.",
      "Type": "A",
      "AliasTarget": {
        "DNSName": "<VPCE_DNS_NAME>",
        "HostedZoneId": "<VPCE_HOSTED_ZONE_ID>",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id <PRIVATE_ZONE_ID> \
  --change-batch file:///tmp/privatelink-dns.json \
  --profile <PROFILE>
```

**5. Verify connectivity**

```bash theme={"dark"}
kubectl run curl-test --restart=Never --image=curlimages/curl:latest -n default \
  --command -- sh -c "curl -sk -o /dev/null -w '%{http_code}' \
  --connect-timeout 10 https://deployment-manager-privatelink.xpander.ai/health; echo"

sleep 15 && kubectl logs curl-test
# Expected output: 200

kubectl delete pod curl-test
```

A timeout or `000`? See [Troubleshooting](https://docs.xpander.ai/self-hosted/hybrid-troubleshooting.md#privatelink).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.