Skip to content

Web App Installation

Minh Tu Le edited this page Aug 4, 2026 · 2 revisions

The Gateway needs a Twingate Network and other components (Client and Connector) to function. For a step-by-step setup from scratch, see the Quick Start Guide. See Overview for the underlying concepts.

Installation with Twingate Operator

The Gateway Helm Chart is a subchart of the Twingate Operator Helm Chart. Enable the subchart in your values.yaml to install the Gateway together with the Operator. Note that <network-name> is the name of your Twingate Network i.e. you access Twingate via https://<network-name>.twingate.com.

twingateOperator:
  # Existing Twingate Operator configurations
  network: "<network-name>"
  remoteNetworkId: "<the ID of the Remote Network>"

gateway:
  enabled: true  # Enable the Gateway subchart
  twingate:
    network: "<network-name>"
  tls:
    dnsNames:
      # Include the resource alias and address in the TLS certificate served by the Gateway
      - "app.int"                                # resource alias
      - "internal-app.default.svc.cluster.local" # resource address
  webApp:
    # Enable the web app proxy
    enabled: true
    # Headers injected into every proxied request, carrying the user's identity
    requestHeaders:
      Authorization: "Bearer {{jwt}}"

Apply this configuration to install the Gateway:

helm upgrade twop oci://ghcr.io/twingate/helmcharts/twingate-operator --install --wait -f ./values.yaml

This installs the Gateway with the web app proxy enabled and configures the Operator to reconcile the web app resources you create below.

TLS certificate

The Twingate Client reaches the Gateway over TLS, and the Gateway serves a certificate that the Client validates against the hostname it dials. The chart provisions a self-signed certificate into the <release>-gateway-tls Secret (for example twop-gateway-tls) and registers its CA with Twingate, so the Client trusts the Gateway without a public CA. The certificate's Subject Alternative Names (SANs) come from gateway.tls.dnsNames, and the Client accepts the connection only if the hostname it dials is listed there. Each resource's alias and address must therefore be covered by a SAN.

This TLS terminates between the Twingate Client and the Gateway. In the browser you still use plain http://<alias>; the Client intercepts that request and tunnels it to the Gateway over the TLS connection above.

Covering multiple web apps with a wildcard

Listing every alias and address individually means reissuing the certificate each time you add an app. Wildcard SANs cover many hostnames at once, so a single certificate can serve all your web apps. Adopt a shared suffix such as acme.internal for your aliases, and cover the cluster-internal addresses with a wildcard:

gateway:
  tls:
    dnsNames:
      - "*.acme.internal"        # all app aliases
      - "*.svc.cluster.local"    # all app addresses

Each web app stays a concrete resource with a concrete alias (for example billing.acme.internal) and address (for example billing.default.svc.cluster.local). Both are covered by the wildcards, so adding an app needs no certificate change.

Wildcard address for a multitenant web app

The alias users type must be concrete, but a resource's address may be a wildcard, letting a single Web App resource reach many upstreams that share a naming pattern:

apiVersion: twingate.com/v1beta
kind: TwingateResource
metadata:
  name: tenant-app
spec:
  type: WebApp
  name: "Tenant App"
  alias: "tenant-app.acme.internal"
  address: "*.svc.cluster.local"
  gatewayRef:
    name: twop-gateway
  downstream:
    port: 80
  upstream:
    port: 8080

The wildcard address must be covered by a matching SAN in tls.dnsNames (here, *.svc.cluster.local).

Configuring identity headers

The Gateway forwards the Twingate user's identity to the upstream application by injecting HTTP request headers. Headers are defined in two places.

Gateway-wide headers

webApp.requestHeaders apply to every web app the Gateway proxies:

webApp:
  requestHeaders:
    Authorization: "Bearer {{jwt}}"

Note

An application consumes the forwarded identity in one of two ways, so configure the headers for one approach rather than both:

  • Verify the signed JWT: inject Authorization: Bearer {{jwt}} and validate it (see The forwarded token).
  • Trust the Gateway and read plain headers: inject X-Twingate-User: {{username}} and X-Twingate-Groups: {{groups}}, which the application reads directly.

See the Overview for the trade-offs.

Header values support the following identity placeholders:

  • {{jwt}} - the signed GAT carrying the full identity (see The forwarded token)
  • {{username}} - the user's Twingate username
  • {{groups}} - the user's groups, comma-separated
  • {{clientGeoLatLong}}, {{clientGeoCity}}, {{clientGeoRegion}}, {{clientGeoCountry}} - client geo attributes

Per-resource header rewrites

requestHeaderRewrites are configured on an individual resource and apply only to that application, letting it receive a header in the exact form it expects. They are applied after the Gateway-wide headers, so they override a header of the same name.

With the Twingate Operator, set them on the TwingateResource as a list of name/value pairs:

requestHeaderRewrites:
  - name: X-ACME-Token
    value: "Bearer {{ jwt }}"

You can also manage these rewrites directly on the resource in the Twingate Admin Console or via the Twingate API.

The forwarded token

{{jwt}} injects the GAT itself: a JWT signed by Twingate (ES256) whose claims include the username, groups, device geo, and the resource being accessed. The upstream application verifies it against Twingate's public keys published at https://<network-name>.twingate.com/api/v1/jwk/ec, checking the issuer, the audience (your network), and the token type (GAT). Because the token is signed, the application can trust the identity without a shared secret.

The GAT is a standard JWT in compact form (header.payload.signature). Decoded header:

{
  "alg": "ES256",
  "typ": "GAT",
  "kid": "a1b2c3d4"
}

Decoded payload (irrelevant claims are omitted for brevity):

{
  "iss": "twingate",
  "aud": "acme",
  "iat": 1735688700,
  "exp": 1735689600,
  "ver": "1",
  "user": {
    "id": "VXNlcjoxMjM0NQ==",
    "username": "alex@acme.com",
    "groups": ["engineering", "on-call"]
  },
  "device": {
    "id": "RGV2aWNlOjk4NzY=",
    "location": {
      "lat": 37.77,
      "lon": -122.42,
      "country": "US",
      "region": "California",
      "city": "San Francisco"
    }
  },
  "resource": {
    "id": "UmVzb3VyY2U6NTU1",
    "type": "WEB_APP",
    "address": "internal-app.default.svc.cluster.local",
    "aliases": ["app.int"],
    "gateway_metadata": {
      "downstream": { "port": 80 },
      "upstream": { "port": 8000 }
    }
  }
}

Creating the Web App resource

Create a TwingateResource of type WebApp. The operator reconciles it into a Twingate resource bound to the Gateway via gatewayRef.

apiVersion: twingate.com/v1beta
kind: TwingateResource
metadata:
  name: acme-app
spec:
  type: WebApp
  name: "ACME App"
  address: "internal-app.default.svc.cluster.local"
  alias: "app.int"
  gatewayRef:
    name: twop-gateway
  downstream:
    port: 80                # browser-facing port
  upstream:
    port: 8000              # port the application listens on
  requestHeaderRewrites:    # optional per-resource headers
    - name: X-ACME-Token
      value: "Bearer {{ jwt }}"
  • address is the address of the application that the Gateway can reach. It must be included in the Gateway's tls.dnsNames.
  • alias is the friendly DNS name users type in their browser. It must also be included in the Gateway's tls.dnsNames.
  • downstream.port is the browser-facing port; upstream.port is the port the application listens on.
  • requestHeaderRewrites are per-resource headers injected in addition to the Gateway-wide requestHeaders.

Apply the resource:

kubectl apply -f resource.yaml

After a few moments, a resource should be created and visible in Twingate Admin Console. Assign your user or group to the resource.

Instead of a standalone TwingateResource, you can annotate the application's Service and let the operator create the resource for you. Set the ports with the resource.twingate.com/downstreamPort and resource.twingate.com/upstreamPort annotations; upstreamPort defaults to the Service's port when it exposes a single one. See the operator's Annotating a Service guide for the full list.

Clone this wiki locally