How do I route all providers through the Private Network Agent automatically?

Last updated: October 19, 2026

Goal

Set up one Proxy on a control plane so that every provider, including ones

installed later or pulled in by a Configuration, goes through the Private

Network Agent with no further changes.


Environment

- Upbound Cloud Spaces control planes with the Private Network Agent.

- Proxy in spaces.upbound.io/v1alpha1


Prerequisites

- Admin role on the control plane, which creating a Proxy requires

- Private Network Agent installed in your private network

- The up CLI, kubectl and jq

- Permission to create robots in your Upbound organization

- No providers installed on the control plane yet, if you are starting fresh


Steps

Create the Proxy with a podSelector on the pkg.crossplane.io/provider

label. The selector is evaluated every time a pod is created on the control

plane, and Crossplane puts that label on every provider pod, so providers that

don't exist yet are covered too. Matching pods get HTTP_PROXY, HTTPS_PROXY

and NO_PROXY injected.

All outbound traffic from a matched provider goes through the agent. That includes calls to cloud provider APIs. If the private network can't reach those endpoints, or the agent is down, matched providers can't reach their targets. Exclude any provider that shouldn't be routed (see Notes).

1. Create a robot and token for the proxy

The robot only needs to belong to your organization, not to any team. A

dedicated robot with no team memberships keeps the token limited to

establishing the proxy connection.

up robot create private-network-proxy --description "Private Network Agent proxy"
up robot token create private-network-proxy proxy-token -f proxy-token.json

2. Store the token on the control plane

up ctx <org>/<space>/<group>/<control-plane>
   
kubectl -n default create secret generic robot-token \
  --from-literal=token="$(jq -r .token proxy-token.json)"
rm proxy-token.json

3. Create the Proxy before installing providers

agentID is optional. Leave it out and one is generated for you, shown in

status.agentID.

apiVersion: spaces.upbound.io/v1alpha1
kind: Proxy
metadata:
  name: private-net
  namespace: default
spec:
  description: "Route all providers through the Private Network Agent"
  agentID: "<your-agent-id>"   # optional; omit to auto-generate (see status.agentID)
  secretRef:
    name: robot-token
    key: token
  podSelector:
    matchExpressions:
      - key: pkg.crossplane.io/provider
        operator: Exists
kubectl apply -f proxy.yaml
kubectl get proxy private-net -n default   # wait for PHASE=Connected

4. Install providers or Configurations as usual

kubectl apply -f <configuration>.yaml

Verify

Every provider pod should show the proxy URL. <none> means the pod was not

injected.

kubectl get pods -n crossplane-system -l pkg.crossplane.io/provider \
  -o custom-columns='POD:.metadata.name,PROVIDER:.metadata.labels.pkg\.crossplane\.io/provider,HTTPS_PROXY:.spec.containers[0].env[?(@.name=="HTTPS_PROXY")].value'
POD                                                         PROVIDER                      HTTPS_PROXY
upbound-provider-helm-f131ef928c22-7d5cff6777-stxwz         upbound-provider-helm         http://proxy-private-net.default.svc.cluster.local:8080
upbound-provider-kubernetes-aeab21ef60b7-7c558dd875-wmpkf   upbound-provider-kubernetes   http://proxy-private-net.default.svc.cluster.local:8080

Excluding Providers

Label selectors don't support wildcards or prefixes, but you can exclude specific providers with NotIn. Values are Provider object names (metadata.name), for example upbound-provider-helm.

  podSelector:
    matchExpressions:
      - key: pkg.crossplane.io/provider
        operator: Exists
      - key: pkg.crossplane.io/provider
        operator: NotIn
        values: [upbound-provider-helm]

To route only a known set of providers instead, use operator: In with their names. You can list providers that aren't installed yet.


Notes

  • All outbound traffic of matched providers goes through the agent, including calls to cloud provider APIs. Make sure the private network can reach those endpoints, or exclude those providers. If the agent is unavailable, matched providers can't reach their targets.

  • One Proxy per control plane. Choose a selector that covers everything the control plane needs.

  • Injection requires PHASE=Connected. Existing matching pods are restarted automatically when the Proxy connects or its selector changes.

  • Custom pod labels are not supported through DeploymentRuntimeConfig on Upbound Cloud, so select on the pkg.crossplane.io/provider label as shown above.

  • Functions are not matched by this selector; they carry pkg.crossplane.io/function instead.


Related

- Private Network Agent

- Kubernetes label selectors