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

# Direct IP

> Route traffic directly to your worker nodes using Gateway API and DNS

With direct IP routing, traffic reaches your applications through your worker nodes' public IP addresses. You point your domains at your workers via DNS, and a <Tooltip headline="Gateway API" tip="Gateway API is the Kubernetes standard for managing external traffic routing. It replaces older Ingress resources with a more expressive, role-oriented model.">Gateway API</Tooltip> controller on the cluster routes requests to the correct application based on the hostname.

This approach is ideal for **self-hosted clusters** where you manage the nodes and have direct access to their public IPs.

## How it works with CNAP-managed clusters

Clusters created through CNAP use <Tooltip headline="Cilium" tip="Cilium is a networking, observability, and security solution for Kubernetes that uses eBPF. CNAP uses it as the default CNI (Container Network Interface) — the plugin responsible for pod networking and traffic routing.">Cilium</Tooltip> as the <Tooltip headline="CNI" tip="Container Network Interface — the plugin that handles networking for pods in a Kubernetes cluster. Different CNIs provide different capabilities. Cilium, Calico, and Flannel are common examples.">CNI</Tooltip>, which includes a built-in Gateway API controller. Cilium's envoy proxy runs on every worker via host network mode, listening on ports 80 and 443:

```
Client → worker-node-ip:80 → Cilium envoy (host network) → HTTPRoute match → Service → Pod
```

The envoy proxy checks the `Host` header against all configured <Tooltip headline="HTTPRoute" tip="An HTTPRoute is a Gateway API resource that matches incoming HTTP requests by hostname and path, then routes them to a Kubernetes Service.">HTTPRoutes</Tooltip> and forwards matching traffic to the correct application, regardless of which node the app's pod runs on.

## Imported and cloud-hosted clusters

If you import an existing cluster into CNAP, the Gateway API controller depends on your Kubernetes setup:

* **Cloud providers** (GKE, EKS, AKS) — typically include their own Gateway API controller backed by cloud load balancers. Traffic goes through the cloud's load balancer rather than directly to worker node IPs, so you don't need to manage A records yourself.
* **Self-hosted with Cilium** — works the same as CNAP-managed clusters. Deploy a Cilium Gateway from your cluster's detail page.
* **Other CNIs** — consult the documentation for your CNI or Kubernetes distribution to set up a Gateway API controller. The HTTPRoute workflow stays the same, only the underlying controller and how traffic enters the cluster differs.

## Prerequisites

* A cluster with a Gateway API controller (Cilium Gateway for CNAP-managed and self-hosted clusters, or a cloud-provided controller)
* One or more worker nodes with public IP addresses (for direct IP setups)
* A domain you control with access to its DNS settings

## Set up a custom domain

<Steps>
  <Step title="Expose a port">
    For Docker and GitHub apps, go to **Settings > Ports** or the app creation flow and toggle **Expose externally** on the port you want to make public. Enter your domain (for example, `app.yourdomain.com`) as the hostname.

    For custom Helm charts, configure the HTTPRoute hostname through the chart's own values (the field name varies by chart).
  </Step>

  <Step title="Set up DNS">
    The gateway runs on every worker node, so any worker with a public IP can receive traffic. Choose a DNS strategy based on your setup:

    <Tabs>
      <Tab title="Single worker">
        Create one A record pointing your domain at the worker's public IP:

        | Record type | Name                 | Value              |
        | ----------- | -------------------- | ------------------ |
        | A           | `app.yourdomain.com` | Worker's public IP |
      </Tab>

      <Tab title="Round-robin">
        Add an A record for each worker. DNS automatically distributes requests across them:

        | Record type | Name                 | Value              |
        | ----------- | -------------------- | ------------------ |
        | A           | `app.yourdomain.com` | Worker 1 public IP |
        | A           | `app.yourdomain.com` | Worker 2 public IP |
        | A           | `app.yourdomain.com` | Worker 3 public IP |
      </Tab>

      <Tab title="Shared base record">
        Create A records for a base hostname (for example, `workers.eu.yourdomain.com`) pointing to all workers in a region, then CNAME your app domains to it. When you add or remove workers, you only update the base record:

        | Record type | Name                        | Value                       |
        | ----------- | --------------------------- | --------------------------- |
        | A           | `workers.eu.yourdomain.com` | Worker 1 public IP          |
        | A           | `workers.eu.yourdomain.com` | Worker 2 public IP          |
        | CNAME       | `app.yourdomain.com`        | `workers.eu.yourdomain.com` |
      </Tab>
    </Tabs>

    DNS propagation can take a few minutes to a few hours depending on your provider and TTL settings.
  </Step>

  <Step title="Verify">
    Once DNS has propagated, open `http://app.yourdomain.com` in your browser. The gateway routes the request to your application based on the hostname.
  </Step>
</Steps>

## Test before DNS propagation

You can verify that the gateway routing works correctly before creating DNS records. Use `curl` with an explicit `Host` header to simulate what a browser does after DNS resolves:

```bash theme={null}
curl -H 'Host: app.yourdomain.com' http://<server-ip>/
```

Replace `<server-ip>` with your worker node's public IP and `app.yourdomain.com` with the hostname you configured.

* **If you see your app's response** — the routing is correct. Create the DNS record and you're done.
* **If you get a 404 from envoy** — the hostname doesn't match any HTTPRoute. Double-check the hostname in your app's port configuration matches exactly what you're passing in the `Host` header.

This works because HTTP routing is based entirely on the `Host` header. The `curl` command sends the same header a browser would send after resolving your domain through DNS — the only difference is you're connecting directly to the server IP instead of going through DNS.

## Troubleshooting

<AccordionGroup>
  <Accordion title="404 Not Found from envoy">
    The gateway is running but no HTTPRoute matches the request's `Host` header. Check that:

    * The hostname in your app's port configuration matches your domain exactly
    * The port has **Expose externally** toggled on
    * The HTTPRoute was created (check the app's status)
  </Accordion>

  <Accordion title="Connection refused on port 80">
    The gateway's envoy proxy isn't running on the worker node. Verify that:

    * A Cilium Gateway is deployed to the cluster
    * The gateway pod is running and healthy
    * Host network mode is enabled in the Cilium configuration
  </Accordion>

  <Accordion title="Gateway status shows AddressNotAssigned">
    This is normal with Cilium's host network mode. The gateway creates a ClusterIP service (not a LoadBalancer), so the status never reports an external IP. Traffic still flows correctly through the host network envoy proxy.
  </Accordion>

  <Accordion title="DNS not resolving">
    DNS propagation can take time. While waiting, use the `curl` test above to confirm routing works. If `curl` returns your app, the issue is DNS propagation — not routing.
  </Accordion>
</AccordionGroup>

## Related topics

* [External access overview →](/networking) — Compare all options for exposing apps
* [Generic application chart →](/tools/generic-chart) — How CNAP deploys applications with Helm
