Create a VPC-backed subnet in VCFA 9 and NSX assigns the default gateway for you — the first usable address in the CIDR. Try to change it in NSX Manager and the field is greyed out. It looks like a platform limitation. It isn’t. The gateway is fully configurable, at creation and afterwards, through the Supervisor API. This post covers the whole path: installing the CLI, establishing organization and namespace context, creating a subnet with a custom gateway, modifying an existing one, and verifying that NSX actually realised the change.
1. Why the NSX Manager field is disabled · 2. Prerequisites · 3. Establishing context with the VCF CLI · 4. Understanding the two Subnet kinds · 5. Creating a subnet with a custom gateway · 6. Modifying an existing subnet · 7. Verifying realisation · 8. Choosing a valid CIDR
1. Why the NSX Manager field is disabled
VPC-backed subnets in VCFA 9 are managed declaratively. The Kubernetes object is the source of truth, and a controller reconciles it into NSX continuously. Editing the realised configuration in NSX Manager sits outside that loop — so even when a change appears to save, the controller restores whatever the object specifies. That is also why the value reverts on refresh: a symptom that reads like a defect but is the reconciliation loop working correctly.
The rule that follows is worth carrying beyond this specific case: in a declarative system, a greyed-out control usually means you are looking at realised state rather than desired state. The edit belongs in the object.
2. Prerequisites
| Requirement | Where to get it |
|---|---|
VCF CLI (the vcf command) | Broadcom Support portal. It supersedes the older kubectl cci plugin. |
| kubectl | Any recent release. The VCF CLI generates the kubeconfig it uses. |
| An API token | VCF Automation org portal → user menu → API Tokens → Generate. Displayed once; copy it immediately. |
| Namespace access | A role that permits creating objects in a Supervisor Namespace. Read-only consumers can follow along but not apply. |
Commands below are PowerShell on Windows. They work identically on Linux and macOS with the usual quoting differences.
3. Establishing context with the VCF CLI
Store the token in a variable. The quotes matter — without them PowerShell attempts to execute the token as a command.
$vcfa_token = "your-api-token-here"
Create the organization context. This is the CLI’s term for a saved connection to a VCF Automation tenant.
vcf context create my-org `
--endpoint https://vcfa.example.com `
--type cci `
--auth-type basic `
--tenant-name My-Org `
--api-token $vcfa_token `
--insecure-skip-tls-verify
Substitute your endpoint and tenant name. Use --insecure-skip-tls-verify only in a lab; in production supply your fleet CA with --ca-certificate instead.
The CLI creates one context for the organization plus one for every Supervisor Namespace you can reach. List them:
kubectl config get-contexts
NAME NAMESPACE
my-org
my-org:app-ns-4hm32:default-project app-ns-4hm32
my-org:phoenix-ns-kj9g6:team-phoenix phoenix-ns-kj9g6
<org>:<namespace>:<project>. Using the bare namespace name returns “no context exists with the name”. Copy the full string from the output above.Switch into the namespace where the subnet will live:
kubectl config use-context my-org:phoenix-ns-kj9g6:team-phoenix
Confirm you are on the namespace layer rather than the organization layer — the presence of subnetsets is a reliable indicator:
kubectl get subnetsets
NAME ACCESSMODE IPV4SUBNETSIZE NETWORKADDRESSES
pod-default PrivateTGW 32
vm-default Private 32 172.30.0.64/27
Unauthorized, as com.vmware.vapi.std.errors.unauthorized, or — confusingly — as a complaint about downloading the OpenAPI schema on an otherwise valid manifest. All three mean the same thing. Re-authenticate with vcf context use my-org, then switch back to your namespace context.4. Understanding the two Subnet kinds
Before writing any YAML, know which object you are creating. VCFA 9 exposes two distinct Subnet kinds in different API groups, corresponding to the two areas of the VCF Automation UI. They are not interchangeable.
| Build & Deploy | Manage & Govern | |
|---|---|---|
| Context | Namespace | Organization |
| API group | crd.nsx.vmware.com/v1alpha1 | vpc.nsx.vmware.com/v1alpha1 |
| Scope | Supervisor Namespace | VPC |
| Name format | db-tier | <vpc>:db-tier |
| Additional required fields | — | regionName, vpcName |
Both support custom gateways, so the technique here applies at either level. Most consumers want the namespace-scoped object; the VPC-scoped one is for governance workflows.
kubectl config current-context before assuming a CRD is missing.5. Creating a subnet with a custom gateway
By default NSX assigns the first usable address in the range — a subnet allocated 172.30.0.192/27 receives gateway 172.30.0.193. To choose a different address you must supply both the CIDR and the gateway, because the gateway is only meaningful relative to a known range.
Namespace-scoped (Build & Deploy):
apiVersion: crd.nsx.vmware.com/v1alpha1
kind: Subnet
metadata:
name: db-tier
namespace: phoenix-ns-kj9g6
spec:
accessMode: Private
ipAddresses:
- 172.30.1.224/27
advancedConfig:
gatewayAddresses:
- 172.30.1.240/27
VPC-scoped (Manage & Govern), run against the organization context:
apiVersion: vpc.nsx.vmware.com/v1alpha1
kind: Subnet
metadata:
name: default-region-west:db-tier
namespace: team-phoenix
spec:
regionName: region-west
vpcName: default-region-west
accessMode: Private
ipAddresses:
- 172.30.1.224/27
advancedConfig:
gatewayAddresses:
- 172.30.1.240/27
kubectl create -f subnet.yaml
create, not apply
kubectl apply writes a last-applied-configuration annotation that this API rejects outright with “Annotation updates are not supported.” Use kubectl create for new objects and kubectl edit or kubectl patch for changes. Inside a VKS guest cluster, ordinary apply works normally — the restriction applies to the VCF Automation API only.The gateway may sit at any usable address within the CIDR. It does not have to be the first.
6. Modifying an existing subnet
Contrary to what the greyed-out NSX Manager field suggests, the gateway can also be changed after creation:
kubectl edit subnet db-tier
Or without an interactive editor:
kubectl patch subnet db-tier --type=merge `
-p '{"spec":{"advancedConfig":{"gatewayAddresses":["172.30.1.250/27"]}}}'
Changing the gateway alters routing for every workload on the subnet, so treat it as a change with a maintenance window rather than a routine edit.
7. Verifying realisation
This is the step most worth building into your habits, and it generalises well beyond gateways.
kubectl create and kubectl edit return success the moment the Kubernetes API accepts the object. NSX realisation happens afterwards, asynchronously, and it can fail silently.
kubectl get subnet db-tier -o jsonpath='{.status.conditions}'
A healthy subnet:
[{"reason":"SubnetReady","status":"True","type":"Ready",
"message":"NSX Subnet with DHCPDeactivated has been successfully created/updated"}]
A failed one carries the real NSX error in the condition message:
[{"reason":"SubnetNotReady","status":"False","type":"Ready",
"message":"... IpAddressBlocks path=[...] do not have spare capacity
to allocate CIDR of size 32 and startIp 172.30.0.160."}]
Confirm the realised addressing too, which is where you will see whether your requested gateway was honoured:
kubectl get subnet db-tier -o jsonpath='{.status.networkAddresses}{"\n"}{.status.gatewayAddresses}'
kubectl get subnets with a name, an access mode, and a spec. It simply has no network behind it. Any automation against this API should poll status.conditions rather than trusting the command’s exit code.8. Choosing a valid CIDR
Specifying ipAddresses disables automatic allocation — ipv4SubnetSize is then ignored entirely, and selecting a valid free range becomes your responsibility.
Each access mode draws from a different IP block, and the blocks do not overlap:
| Access mode | Address source | Reachable from |
|---|---|---|
Private | The VPC’s own private range | Within the VPC; SNAT applied on egress |
PrivateTGW | A separate transit-gateway block | Other VPCs on the same transit gateway — routed, not NAT’d |
Public | The NSX Project’s external block | Natively routed beyond the VPC |
Choose from the wrong pool and NSX names the block it expected:
CIDR 172.30.1.64/27 does not belong to IpAddressBlocks
path=[/orgs/default/projects/.../ip-blocks/default-tgw-region-west]
To find free space, list what is already allocated:
kubectl get subnets,subnetsets -o custom-columns=`
'NAME:.metadata.name,CIDR:.status.networkAddresses'
Summary
| Task | Command or field |
|---|---|
| Authenticate | vcf context create <org> --type cci --api-token $token |
| Switch to namespace | kubectl config use-context <org>:<ns>:<project> |
| Set gateway at creation | spec.advancedConfig.gatewayAddresses plus spec.ipAddresses |
| Change it later | kubectl edit subnet <name> |
| Verify | kubectl get subnet <name> -o jsonpath='{.status.conditions}' |
| Find free ranges | kubectl get subnets,subnetsets with status.networkAddresses |
If you are new to this API, the companion post Your First VM and Kubernetes Cluster in VCF Automation walks through projects, namespaces, virtual machines and VKS clusters using the same toolchain.

Leave a Reply