Connect a control plane
Connecting a Kubernetes cluster as a control plane to the Upbound Hub allows you to observe its resources in the Console or Hub API.
To connect control planes managed by Upbound Spaces, see how to connect a Space.
Prerequisites
- A control plane to connect: the Kubernetes cluster whose resources you want to observe, and a kubeconfig context for it.
- Upbound Hub, reachable from the control plane.
- A realm for the control plane, and admin permissions within that realm.
- Access to the Console, or
kubectlconfigured with ahubcontext that targets the Hub. - Helm 3.
Step 1: Declare the control plane
Declare the control plane in Upbound Hub to assign its realm and name, then get a connector registration token.
- Console
- kubectl
-
In the Console, open the navigation menu and choose Control planes under Infrastructure.
-
Select Register Control Plane.
-
Choose the realm that owns the control plane, and enter a name for it. When Hub has only one realm, the Console fills it in for you. The name must be unique within that realm.

-
Select Register. The Console registers the control plane and immediately shows its registration token. Copy it now (see Step 2).
Write the control plane manifest. A control plane is namespaced, and its namespace is the realm:
apiVersion: fleet.hub.upbound.io/v1beta1
kind: ControlPlane
metadata:
name: <control-plane-name>
namespace: <realm>
Create it. The registration token is only returned in the create response, so capture it now (see Step 2):
REGISTRATION_TOKEN=$(kubectl --context=hub create -f controlplane.yaml \
-o jsonpath='{.status.registrationToken}')
Step 2: Get the registration token
Capture the registration token from Step 1. Use it to deploy the connector in Step 3.
The Console and kubectl return the registration token only once, and you can't retrieve it after creation. The token is valid for 24 hours. You can issue a new token at any time, but issuing one doesn't revoke earlier tokens.
After a connector registers the control plane, no other token can register it. To hand the control plane to a new connector, for example after you move the connector to another cluster, deregister the control plane first.
- Console
- kubectl
Copy the registration token displayed after you created the control plane. The Console will not show it again.

Set it as an environment variable for Step 3:
REGISTRATION_TOKEN=<paste-registration-token>
$REGISTRATION_TOKEN from Step 1 holds the token. Confirm it is populated:
echo "$REGISTRATION_TOKEN"
The token from the create response cannot be read back later, but you can issue a new token for the same control plane at any time. Issuing a new token doesn't revoke earlier ones:
REGISTRATION_TOKEN=$(echo '{"apiVersion":"fleet.hub.upbound.io/v1beta1","kind":"ControlPlane"}' \
| kubectl --context=hub create --raw \
"/apis/fleet.hub.upbound.io/v1beta1/namespaces/<realm>/controlplanes/<control-plane-name>/registrationtoken" \
-f - | jq -r '.status.registrationToken')
Step 3: Deploy the connector
Deploy the connector to the control plane. The connector completes the control plane's registration and syncs its resources.
Set the kubeconfig context for the control plane:
CONTROL_PLANE_CONTEXT=<control-plane-context>
Create a namespace and a secret holding the registration token:
kubectl --context="$CONTROL_PLANE_CONTEXT" create namespace upbound-system
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
create secret generic hub-connector-credentials \
--from-literal=registrationToken="$REGISTRATION_TOKEN"
Install the connector chart, pointing it at the Hub:
helm install hub-connector oci://xpkg.upbound.io/upbound/hub-connector \
--version 1.2.0 \
--kube-context "$CONTROL_PLANE_CONTEXT" \
--namespace upbound-system \
--set connector.hub.url=<hub-url>
Notes:
connector.credentials.existingSecretRef.namedefaults tohub-connector-credentials. Override it only if you named the secret differently.connector.sync.limitToClusterRoleslimits which resources the connector syncs to those authorized by the listed ClusterRoles. It defaults tocrossplane-adminwhich only syncs Crossplane resources. Set it to[]to sync all resources.
Step 4: Verify the connector started
Confirm the connector pods reach Ready:
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
wait --for=condition=Ready pod \
--selector app.kubernetes.io/name=hub-connector --timeout=120s
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
get pods --selector app.kubernetes.io/name=hub-connector
Step 5: Observe from Insights
View the control plane and its resources in Insights.
- Console
- kubectl
-
Open the control planes view. The control plane you created shows a status of Ready.

-
Open its resources view to see the resources synced from the control plane.

The control plane reports phase Ready:
kubectl --context=hub --namespace <realm> get controlplanes
kubectl --context=hub --namespace <realm> \
get controlplane <control-plane-name> -o jsonpath='{.status.phase}'
List the resources synced from this control plane. The resources endpoint takes
a controlPlane filter:
kubectl --context=hub get --raw \
'/apis/inventory.hub.upbound.io/v1beta1/resources?pageSize=100&filter=controlPlane==%22<control-plane-name>%22' \
| jq -r '.items[].metadata.name'
Troubleshooting
The connector pods don't reach Ready
Check the pod status and events:
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
describe pod --selector app.kubernetes.io/name=hub-connector
The connector registers the control plane in its resolve-identity init
container, and the pod doesn't start the connector until registration succeeds.
If registration fails, the pod stays in Init or Init:CrashLoopBackOff, and
the control plane stays Pending. Read the init container's logs to see why:
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
logs deployment/hub-connector -c resolve-identity
Common causes:
-
The
hub-connector-credentialssecret is missing, or its token expired before the control plane registered. Issue a new token (Step 2), update the secret, and restart the connector. To update the secret, replace it with the new token:kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \create secret generic hub-connector-credentials \--from-literal=registrationToken="$REGISTRATION_TOKEN" \--dry-run=client -o yaml \| kubectl --context="$CONTROL_PLANE_CONTEXT" apply -f - -
The control plane is already registered to another connector, for example after you reinstall the connector on a different cluster. Deregister the control plane, issue a new token (Step 2), update the secret, and restart the connector.
-
The connector can't reach the Hub. See The connector can't reach the Hub.
To deregister a control plane so a new connector can register it:
kubectl --context=hub delete --raw \
"/apis/fleet.hub.upbound.io/v1beta1/namespaces/<realm>/controlplanes/<control-plane-name>/registration"
To restart the connector after you update the secret:
kubectl --context="$CONTROL_PLANE_CONTEXT" --namespace upbound-system \
rollout restart deployment hub-connector
The control plane stays Pending
A control plane stays Pending until its connector completes registration.
Check the phase:
kubectl --context=hub --namespace <realm> \
get controlplane <control-plane-name> -o jsonpath='{.status.phase}'
The phase changes to Ready as soon as registration succeeds, which is also
when the connector pods can become Ready. If the phase stays Pending, see
The connector pods don't reach Ready.
The connector can't reach the Hub
The connector registers with and pushes to the Hub at connector.hub.url. From
the control plane, confirm that URL is correct and reachable, and that any
firewall or network policy allows egress to the Hub. The connector refuses a
plain http:// URL unless the host is cluster-local, so use https:// for a
Hub outside the cluster. Correct the value and upgrade the release:
helm upgrade hub-connector oci://xpkg.upbound.io/upbound/hub-connector \
--version 1.2.0 \
--kube-context "$CONTROL_PLANE_CONTEXT" \
--namespace upbound-system \
--reuse-values \
--set connector.hub.url=<hub-url>
No resources appear for the control plane
By default the connector syncs only resources authorized by the
crossplane-admin ClusterRole. If the resources you expect aren't covered by
that role, they don't sync. Widen connector.sync.limitToClusterRoles, or set
it to [] to sync everything, then upgrade the release.
Next steps
- Access management: Grant users and groups access to the control plane and its resources.