---
title: API gateway
description: Install, configure, check, and upgrade the openlm-apigateway Helm chart, the API gateway that forwards OpenLM Platform /api requests to its services.
product: OpenLM Platform
---

Install the **API gateway** once per OpenLM Platform installation, in the same namespace, before OpenLM Platform itself. The OpenLM web UIs get their data from `https://<your address>/api/...`, and the API gateway receives every one of these requests and forwards it to the OpenLM Platform service that handles it.

:::tip[In short]
The installation at a glance:

- **What**: A Helm chart, `openlm/openlm-apigateway`, based on Tyk Gateway (open source).
- **You provide**: A Redis, a storage class, and a secret.
- **You run**: Two `kubectl` commands, one values file, and one `helm` command, in about 10 minutes.
- **You check**: The gateway pod runs, its job says `Bootstrap completed successfully`, and the routes are listed.
- **Fixed rule**: The Helm release is always called `openlm-gateway`.
:::

## How it works

```mermaid
flowchart LR
  B[Browser] -->|"https://your-address/api/..."| E[Ingress or OpenShift Route]
  E -->|port 8080| G[API gateway]
  G -->|"/api/licenses → openlm-licenses"| S1[Platform service]
  G -->|"/api/identity... → ..."| S2[Platform service]
  G <--> R[(Redis)]
  J[Bootstrapper job] -->|"control API, port 9699 (inside the cluster only)"| G
```

Each request to `/api` takes these steps:

1. Your Ingress (or OpenShift Route) sends every path that starts with `/api` to the gateway.
2. The gateway looks up the **route** for the path. Each route maps one path, such as `/api/licenses`, to one OpenLM Platform service in the same namespace, such as `openlm-licenses`.
3. The gateway forwards the request and returns the answer. It doesn't check sign-in itself. The OpenLM Platform services do.

The routes come with the chart. After every installation and upgrade, a short **bootstrapper job** loads the routes for that chart version into the gateway. The gateway keeps them on a volume, so they survive a restart. It keeps its working data in Redis.

<details>
<summary>Details for operators</summary>

The gateway is made of these parts:

- **Gateway**: Tyk Gateway 5.9.1 (open source), one Deployment, `gateway-openlm-gateway-tyk-gateway`. It listens on port 8080 for traffic and on a separate port 9699 for its control API. The control API has its own Service and is never exposed outside the cluster.
- **Routes**: Keyless Tyk API definitions with `listen_path` `/api/<service>` and a target of the form `http://openlm-<service>/...`, a short Service name. That is why the gateway must run in the platform's namespace.
- **Bootstrapper job**: `gateway-bootstrapper-job-<revision>`. It waits until `/hello` on the control API reports healthy, deletes **all** API definitions, creates the ones bundled in its image, reloads the gateway and checks health again. It retries for about 5 minutes, with at most 3 attempts, and removes itself 5 minutes after it finishes.
- **State**: API definitions are files on the volume `tyk-app-claim`. Policies and middleware have their own volumes. Each volume is 200 MB, `ReadWriteOnce`, and is kept when you uninstall. Redis holds the gateway's runtime data.
- **Secret**: `APISecret` protects the control API. The gateway and the bootstrapper read it from the same Secret.
</details>

## Before you begin

Confirm these prerequisites:

- [ ] The **namespace** of OpenLM Platform exists. The examples use `openlm`. Any name works, as long as the gateway and OpenLM Platform share it.
- [ ] A **Redis** that the namespace can reach: its host, port, password, and whether it uses TLS. See [Redis](#redis).
- [ ] A **storage class** that provides `ReadWriteOnce` volumes: `kubectl get storageclass`.
- [ ] **Helm** 3 or 4, and `kubectl` (or `oc`) access to the namespace.
- [ ] You know how `/api` reaches the gateway in your setup. Usually the OpenLM Platform Ingress does it, or an OpenShift Route. See [Expose /api](#expose-api).

## Install the API gateway

Follow these steps in order.

### 1. Add the OpenLM Helm repository

Set the namespace, add the repository, and list the latest API gateway release:

```bash
export NS=openlm
helm repo add openlm https://openlm-helm.github.io/helmcharts
helm repo update openlm
helm search repo openlm/openlm-apigateway --devel
```

:::note[Release candidates need `--devel`]
Helm ignores pre-release versions such as `1.0.0-rc.2` unless you add `--devel`. The commands on this page use `--devel`, so they always install the latest API gateway release, the one that goes with every OpenLM Platform release.
:::

If this machine has no internet access, run `helm pull openlm/openlm-apigateway --devel` on a machine that has it. It may print `unable to find exact version requested; falling back to closest available version`, which is harmless. Copy the `.tgz` file over, and use the file in place of `openlm/openlm-apigateway` in the following steps.

### 2. Create the secrets

Create one Secret for the control API and one for the Redis password:

```bash
kubectl -n "$NS" create secret generic openlm-gateway-secrets \
  --from-literal=APISecret="$(openssl rand -hex 32)"
kubectl -n "$NS" create secret generic openlm-gateway-redis \
  --from-literal=password='<your Redis password or access key>'
```

`APISecret` protects the gateway's control API. The chart has no default and stops with a message if you don't provide one. If your Redis has no password, skip the second command and leave out `passSecret` in the next step.

### 3. Write the values file

Write the values file for the standard setup, where the OpenLM Platform Ingress sends `/api` to the gateway and Redis uses TLS:

```yaml
# gateway-values.yaml
storageClassName: <your storage class>
tyk-oss:
  global:
    secrets:
      useSecretName: openlm-gateway-secrets      # the Secret from step 2
    redis:
      addrs: ["<redis host>:<port>"]
      useSSL: true                                # false for a Redis without TLS
      passSecret: { name: openlm-gateway-redis, keyName: password }
      storage: { database: 0 }
  tyk-gateway:
    gateway:
      hostName: <your address>
      ingress: { enabled: false }                 # /api comes through the platform's Ingress
```

On OpenShift, add the Route instead, as described in [Expose /api](#expose-api).

### 4. Install

Install the chart:

```bash
helm upgrade --install openlm-gateway openlm/openlm-apigateway --devel \
  -n "$NS" -f gateway-values.yaml --wait --timeout 10m
```

The release name must be **`openlm-gateway`**. OpenLM Platform sends `/api` to the Service `gateway-svc-openlm-gateway-tyk-gateway`, and that name comes from the release name.

### 5. Check

Check the gateway pod and the bootstrapper job:

```bash
kubectl -n "$NS" get pods | grep -E "gateway"
kubectl -n "$NS" logs -l job-name --tail=200 | grep -E "Bootstrap completed|failed|Error"
```

The output shows the following:

- The gateway pod `gateway-openlm-gateway-tyk-gateway-…` with `1/1` and `Running`.
- A pod `gateway-bootstrapper-job-…` with `Completed`, and the log line `Bootstrap completed successfully`.

The job removes itself 5 minutes after it finishes. To check the routes at any time, see [Check the routes](#check-the-routes).

## Check the routes

Check the routes after an installation or upgrade, or when a page in the web UI shows no data. The routes are in place when the gateway lists one for every OpenLM service.

To list the routes, open a tunnel to the gateway's control API and query it:

```bash
# 1. Open a tunnel to the gateway's control API (leave it running, use a second terminal)
kubectl -n "$NS" port-forward svc/gateway-control-svc-openlm-gateway-tyk-gateway 9699:9699

# 2. Read the secret and ask the gateway
export TYK_SECRET="$(kubectl -n "$NS" get secret openlm-gateway-secrets -o jsonpath='{.data.APISecret}' | base64 -d)"
curl -s localhost:9699/hello                                    # health: "status":"pass"
curl -s -H "x-tyk-authorization: $TYK_SECRET" localhost:9699/tyk/apis \
  | jq -r '.[] | [.proxy.listen_path, .proxy.target_url] | @tsv' | sort
```

In the output, check the following:

- `/hello` reports `"status":"pass"`, with Redis `pass`.
- The list has one line per route: the path first, then the service. Version 1.0.0-rc.2 loads 80 routes. Check that the path a failing page calls is in the list.
- If the list is empty or a path is missing, the bootstrapper job didn't finish. Run the upgrade command from [Upgrade](#upgrade) again with the same version. Each run starts a new job that reloads all routes.

If you don't have `jq`, leave out the last line and search the output for `"listen_path"`.

To check the whole way from outside, sign in to the web UI and open a few pages. Each page loads its data through `/api`.

## Expose /api

To send `/api` to the gateway, use exactly one of these setups:

| Setup | When | What to set |
|---|---|---|
| **Platform Ingress** (standard) | Kubernetes with the platform's single Ingress | `tyk-oss.tyk-gateway.gateway.ingress.enabled: false`. Nothing else: the platform's Ingress already sends `/api` to the gateway. |
| **OpenShift Route** | OpenShift, ROSA, ARO | `openShiftRoute.enabled: true`, `openShiftRoute.host: <your address>`, and the Tyk Ingress off |
| **Gateway Ingress** | The gateway without the platform's Ingress | The Tyk Ingress on, with your host, class and TLS secret |

:::warning[The gateway's own Ingress is on by default]
With chart defaults, the gateway creates an Ingress for the placeholder host `gateway-domain.com`. Set `tyk-oss.tyk-gateway.gateway.ingress.enabled: false` unless you use it.
:::

### OpenShift Route

To create the Route, add these settings to your values file:

```yaml
openShiftRoute:
  enabled: true
  host: <your address>
tyk-oss:
  tyk-gateway:
    gateway:
      ingress: { enabled: false }
```

The chart creates the Route `openlm-gateway` in the release namespace: path `/api`, TLS ends at the router (edge), HTTP is redirected to HTTPS, and timeouts are 600 seconds. It sets the same security headers as the OpenLM Platform Routes: HSTS (one year, including subdomains), `X-Frame-Options: SAMEORIGIN`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, and `Content-Security-Policy: frame-ancestors 'self'`. The headers need OpenShift 4.14 or higher. On older versions, set `openShiftRoute.routeSecurity.enabled: false`.

To check the Route, run `oc -n "$NS" get route openlm-gateway`. If you created an `/api` Route by hand earlier, delete it. With two Routes for the same host and path, OpenShift uses the older one.

### Gateway Ingress

To turn on the gateway's own Ingress, add these settings with your host, class, and TLS secret:

```yaml
tyk-oss:
  tyk-gateway:
    gateway:
      hostName: <your address>
      ingress:
        enabled: true
        className: <your ingress class>
        hosts:
          - host: <your address>
            paths: [{ path: /api, pathType: ImplementationSpecific }]
        tls:
          - secretName: <your TLS secret>
            hosts: [<your address>]
```

The chart sets annotations for ingress-nginx (100 MB request body, 600-second timeouts). If you use another controller, replace `annotations` with its equivalents.

## Redis

Set `addrs`, `useSSL`, and `storage.database` for your Redis:

| Your Redis | `addrs` | `useSSL` | `storage.database` |
|---|---|---|---|
| In your network, no TLS | `["<host>:6379"]` | `false` | a free database, for example `2` |
| With TLS | `["<host>:<TLS port>"]` | `true` | a free database |
| Azure Managed Redis | `["<name>.<region>.redis.azure.net:10000"]` | `true` | `0` |
| Azure Cache for Redis | `["<name>.redis.cache.windows.net:6380"]` | `true` | a free database |

Keep these points in mind:

- Azure Managed Redis: create it with the **non-clustered** policy. It has one database, so `storage.database` is `0`.
- The gateway can share a Redis with OpenLM Platform. Give it its own database number where the Redis has several.
- If your Redis certificate comes from a private CA, contact OpenLM support. For a test installation only, `tyk-oss.global.redis.sslInsecureSkipVerify: true` skips the certificate check.

## Settings reference

Set these settings in your values file. All Tyk settings sit under `tyk-oss:`, and any setting you don't set keeps its default:

| Setting | Default | What it does |
|---|---|---|
| `storageClassName` | (none, required) | Storage class for the three volumes |
| `tyk-oss.global.secrets.useSecretName` | (empty) | Your Secret with the key `APISecret`. One of the two secret settings is required. |
| `tyk-oss.global.secrets.APISecret` | (empty) | Plain-text alternative to `useSecretName` |
| `tyk-oss.global.redis.addrs` | `["openlm-redis.openlm:6379"]` | Redis `host:port` |
| `tyk-oss.global.redis.useSSL` | `false` | TLS to Redis |
| `tyk-oss.global.redis.passSecret.name`, `.keyName` | (empty) | Secret and key with the Redis password |
| `tyk-oss.global.redis.storage.database` | `2` | Redis database number |
| `tyk-oss.tyk-gateway.gateway.hostName` | `gateway-domain.com` | Your address |
| `tyk-oss.tyk-gateway.gateway.ingress.enabled` | `true` | The gateway's own Ingress |
| `tyk-oss.tyk-gateway.gateway.replicaCount` | `1` | Number of gateway pods. More than one gives you no downtime during upgrades. |
| `tyk-oss.tyk-gateway.gateway.resources` | (none) | CPU and memory requests and limits |
| `tyk-oss.tyk-gateway.gateway.nodeSelector`, `.tolerations`, `.affinity` | (none) | Where the gateway pods run |
| `tyk-oss.tyk-gateway.gateway.log.level` | `warn` | `debug`, `info`, `warn` or `error` |
| `tyk-oss.tyk-gateway.gateway.extraEnvs` | 600-second timeouts | Extra Tyk settings as `TYK_GW_*` variables. Replacing the list removes the timeouts: copy them in. |
| `openShiftRoute.enabled` | `false` | Create the OpenShift Route for `/api` |
| `openShiftRoute.host` | (empty) | Your address. Required when the Route is on. |
| `openShiftRoute.name` | `openlm-gateway` | Name of the Route, for example to take over an existing one |
| `openShiftRoute.routeSecurity.enabled` | `true` | HSTS and security headers on the Route |
| `openShiftRoute.routeSecurity.hstsMaxAge` | `31536000` | HSTS max-age, in seconds |
| `openShiftRoute.routeSecurity.hstsIncludeSubDomains` | `true` | HSTS `includeSubDomains` |
| `bootstrapperJob.image` | `public.ecr.aws/r3q3q2f4/olm-gateway-bootstrapper:<version>` | Image with the routes. Each chart version pins its own. |
| `bootstrapperJob.affinity`, `.tolerations` | (none) | Where the job runs. The job has no `nodeSelector` setting: use `affinity`. |
| `bootstrapperJob.argocd.enabled` | `false` | `true` when Argo CD deploys the chart: the job then runs as a PostSync hook and the application stays in sync |

<details>
<summary>Private registry (air-gapped)</summary>

Mirror these images and point the chart at your copies:

| Image | Setting |
|---|---|
| `docker.tyk.io/tyk-gateway/tyk-gateway:v5.9.1` | `tyk-oss.tyk-gateway.gateway.image.repository` and `.tag` |
| `busybox:1.32` | `tyk-oss.tyk-gateway.gateway.initContainers.setupDirectories.repository` |
| `public.ecr.aws/r3q3q2f4/olm-gateway-bootstrapper:<version>` | `bootstrapperJob.image` (full reference) |

Set `tyk-oss.tyk-gateway.gateway.imagePullSecrets` for the gateway. The bootstrapper job runs with the namespace's `default` service account: add your pull secret to that service account. `helm test` uses `zalbiraw/alpine-curl-jq`, and only when you run `helm test`.
</details>

## Upgrade

Read the [release notes](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/release-notes/api-gateway) of the new version first. Then upgrade the release:

```bash
helm -n "$NS" get values openlm-gateway > gateway-values.yaml   # only if you no longer have the file
helm repo update openlm
helm upgrade openlm-gateway openlm/openlm-apigateway --devel \
  -n "$NS" -f gateway-values.yaml --wait --timeout 10m
```

Each upgrade runs the bootstrapper job again, which replaces all routes with the ones of the new version. After the upgrade, [check the routes](#check-the-routes).

### Upgrade from openlm-gateway 0.2.x

Up to 0.2.x, the chart was called `openlm-gateway` and came as a file. Upgrade the **same release** with the new chart, and keep the release name `openlm-gateway`.

First, check whether your installation has its own secret:

```bash
helm -n "$NS" get values openlm-gateway | grep -E "useSecretName|APISecret"
```

- If it shows `useSecretName`, or an `APISecret` you chose yourself, upgrade with the command in [Upgrade](#upgrade). No objects change and the gateway isn't restarted.
- If it shows nothing, or an `APISecret` you didn't choose, you need your own secret. Create the Secret from [Install, step 2](#2-create-the-secrets), add `useSecretName: openlm-gateway-secrets` to your values file, then upgrade. The gateway restarts once, so `/api` is unavailable for a few seconds. Plan a short maintenance window, and update any script of your own that calls the control API with the old secret.

If the upgrade stops with *"The gateway needs its own APISecret"*, nothing has changed in the cluster. Create the Secret and run it again.

Don't install the new chart under a new release name. That creates a second gateway with other Service names, and `/api` keeps going to the old one.

## Operate

Rotate the secrets, read the logs, or uninstall the gateway.

### Rotate the secrets

The gateway reads both secrets when it starts, so restart it after a change:

```bash
kubectl -n "$NS" create secret generic openlm-gateway-secrets \
  --from-literal=APISecret="$(openssl rand -hex 32)" --dry-run=client -o yaml | kubectl -n "$NS" apply -f -
kubectl -n "$NS" rollout restart deployment gateway-openlm-gateway-tyk-gateway
```

Rotate the Redis password the same way with `openlm-gateway-redis`: change it in Redis, update the Secret, restart. The gateway can't reach Redis between the first and the last step.

### Read the logs

To read the gateway's logs, run `kubectl -n "$NS" logs deploy/gateway-openlm-gateway-tyk-gateway`. For more detail, set `tyk-oss.tyk-gateway.gateway.log.level: debug` and upgrade. Set it back afterward.

### Uninstall

Uninstall the release. Delete the volumes only if you want to remove them as well:

```bash
helm -n "$NS" uninstall openlm-gateway
kubectl -n "$NS" delete pvc tyk-app-claim tyk-policies-claim tyk-middleware-claim   # only to remove the volumes as well
```

The volumes and your two Secrets stay unless you delete them.

## Troubleshooting

If the gateway doesn't work, find what you see in this table:

| What you see | Likely cause | What to do |
|---|---|---|
| `helm` stops: *The gateway needs its own APISecret* | No secret set, or the one shipped with 0.2.x | [Install, step 2](#2-create-the-secrets), then set `useSecretName` |
| `helm` stops: *Please define storageClassName to be used* (before 1.0.0-rc.2: *Pelase define…*) | `storageClassName` missing | Set it to one of `kubectl get storageclass` |
| Pods wait in `Pending`, volumes `Pending` | The storage class can't provide `ReadWriteOnce` volumes | Use another storage class |
| Gateway pod restarts, log mentions `redis` | Wrong host, port, password, or TLS setting | Check `addrs`, `useSSL` and the Secret `openlm-gateway-redis`. Azure: port 10000 or 6380, TLS on |
| Log or job mentions `CROSSSLOT` | Azure Managed Redis with a clustered policy | Recreate the cache with the non-clustered policy |
| Bootstrapper job fails after about 5 minutes | Gateway not healthy, usually Redis | Fix the gateway first, then run the upgrade again |
| Web UI loads, but pages show no data, and `/api/...` returns 404 | Routes not loaded | [Check the routes](#check-the-routes), then run the upgrade again |
| `/api/<service>` returns 502 or 503 | That service isn't running, or the gateway is in another namespace than the platform | Run `kubectl -n "$NS" get pods`, and install the gateway in the platform's namespace |
| OpenShift: `/api/...` shows the home page | No Route for `/api` | Set `openShiftRoute.enabled: true`, or check `oc get route` |
| Two gateways after an upgrade | The new chart was installed under a new release name | Uninstall the new release, and upgrade `openlm-gateway` instead |

## Technical reference

<details>
<summary>Objects the chart creates</summary>

The chart creates these objects:

| Kind | Name | Notes |
|---|---|---|
| Deployment | `gateway-openlm-gateway-tyk-gateway` | Tyk Gateway 5.9.1, init container `busybox` prepares the directories. Runs as non-root with a read-only root file system. |
| Service | `gateway-svc-openlm-gateway-tyk-gateway` | Port 8080, traffic. The platform's Ingress and the Route point here. |
| Service | `gateway-control-svc-openlm-gateway-tyk-gateway` | Port 9699, control API. Inside the cluster only. |
| Job | `gateway-bootstrapper-job-<revision>` | Loads the routes. Removed 5 minutes after it finishes. |
| PersistentVolumeClaim | `tyk-app-claim`, `tyk-policies-claim`, `tyk-middleware-claim` | 200 MB each, `ReadWriteOnce`, kept on uninstall |
| Secret | `secrets-openlm-gateway-tyk-gateway` | Created by the chart for values you set in plain text (`APISecret`, Redis password). Not needed when you use `useSecretName` and `passSecret`. |
| Secret | `tyk-operator-conf` | May be created for Tyk tooling. Holds no secret values when you use `useSecretName`. |
| Ingress | `openlm-gateway-tyk-gateway` | Only with `ingress.enabled: true` |
| Route | `openlm-gateway` (or `openShiftRoute.name`) | Only with `openShiftRoute.enabled: true` |
</details>

<details>
<summary>Security notes</summary>

Keep these security points in mind:

- The control API (port 9699) can change every route. Keep it inside the cluster: no Ingress, Route or `LoadBalancer` for `gateway-control-svc-…`. If you use NetworkPolicies, allow port 9699 only from the bootstrapper job's pods.
- `APISecret` is the key to the control API. Keep it in a Secret (`useSecretName`) rather than in your values file, and rotate it as in [Operate](#operate).
- The routes are keyless: the gateway forwards requests without checking sign-in, and each platform service checks the user's token itself.
- On OpenShift, the Route adds HSTS and the security headers listed in [Expose /api](#expose-api).
</details>
