This guide shows how to route outbound traffic from pods through a specific Kubernetes node using Cilium’s Egress Gateway functionality. This is useful when you need predictable egress IPs, for example, for firewall allowlists or compliance. {% callout type="warning" %} This guide assumes your egress gateway node has a **static public IP** assigned to the host. On Hetzner, cloud VMs will receive a different public IP after cluster upgrades or node reprovisioning. To avoid this, use bare-metal servers as your egress gateway nodes. As an added benefit, Hetzner bare-metal machines offer significantly higher network bandwidth: up to 10 Gbit/s dedicated NIC, compared to max 1 Gbit/s on cloud instances. {% /callout %} ## Step 1: Label the Egress Gateway Node The label acts as a selector that the Egress Gateway Policy will  use to identify which node should handle the outbound traffic. Without this label, the policy wouldn’t know which node is eligible to route egress traffic, and the setup would not function as intended. For adding a persistent label to the node, add it to the metadata section of your machine deployment in the Cluster resource. This ensures that the label is applied to the node even after a node replacement or upgrade. Example cluster definition: ```yaml apiVersion: cluster.x-k8s.io/v1beta1 kind: Cluster metadata: name: egress-testing spec: clusterNetwork: services: cidrBlocks: ['10.128.0.0/12'] pods: cidrBlocks: ['192.168.0.0/16'] serviceDomain: 'cluster.local' topology: class: hetzner-apalla-1-34-v6 version: v1.34.6 controlPlane: replicas: 1 workers: machineDeployments: - class: workeramd64hcloud name: md-cloud replicas: 3 failureDomain: nbg1 variables: overrides: - name: workerMachineTypeHcloud value: cpx42 - class: workeramd64baremetal name: md-baremetal replicas: 1 metadata: // [!code ++] labels: // [!code ++] node-role.kubernetes.io/egress-gateway: "true" // [!code ++] variables: - name: region value: nbg1 - name: controlPlaneMachineTypeHcloud value: cpx42 ``` For more information on node labeling, please refer to the [Labelling and assign roles on nodes](/docs/hetzner/apalla/how-to-guides/server-management/labelling-and-assign-roles-on-nodes) page. ## Step 2: Create the Egress Gateway Policy The Egress Gateway Policy is a Kubernetes resource that defines how outbound traffic should be routed. It specifies the node label to match and the static IP address to use for egress traffic. Replace the `egressIP` value with the actual external IP of your egress gateway node. For finding the external IP of your node, you can use the following command: ```bash kubectl get nodes -l node-role.kubernetes.io/egress-gateway=true -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}' ``` Sample Egress Gateway Policy YAML: ```yaml /// egress-gateway-policy.yaml apiVersion: cilium.io/v2 kind: CiliumEgressGatewayPolicy metadata: name: force-egress-via-node spec: selectors: - podSelector: matchLabels: app: my-app destinationCIDRs: - 0.0.0.0/0 egressGateway: nodeSelector: matchLabels: node-role.kubernetes.io/egress-gateway: "true" egressIP: 135.119.210.110 ``` You can save this YAML to a file named `egress-gateway-policy.yaml` and apply it using `kubectl`: ```bash kubectl apply -f egress-gateway-policy.yaml ``` In this example, the policy will route all outbound traffic from pods with the label `app: my-app` through the node labeled with `node-role.kubernetes.io/egress-gateway: "true"`. There are additional options available for an Egress Gateway Policy, such as specifying multiple node labels or CIDR ranges. For more details, refer to the [Cilium documentation](https://docs.cilium.io/en/stable/network/egress-gateway/egress-gateway/#writing-egress-gateway-policies). ## Step 3: Test the Egress Gateway (Optional) To verify that the egress traffic is being routed through the specified node, you can deploy a test pod in the non-egress node and check its outbound IP address: ```yaml /// test-pod.yaml apiVersion: v1 kind: Pod metadata: name: egress-test labels: app: my-app spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/egress-gateway operator: DoesNotExist containers: - name: curl image: curlimages/curl:8.5.0 command: ["sleep", "3600"] restartPolicy: Never ``` Save this YAML to a file named `test-pod.yaml` and apply it using `kubectl`: ```bash kubectl apply -f test-pod.yaml ``` Once the pod is running, you can exec into it and check the outbound IP address. The following command uses `ifconfig.me`, a simple web service that returns the public IP address of the client making the request: ```bash kubectl exec -it egress-test -- curl -s ifconfig.me ``` The output will show the IP address as seen by external services. If you see the static IP address you specified in the Egress Gateway Policy, it means the setup is working correctly.