# Connect to an AWS ALB with mTLS

<EnterpriseFeature name="mTLS Client Certificates" />

When your backend sits behind an AWS Application Load Balancer (ALB), you can
lock the ALB down so it only accepts requests that prove they came from your
Zuplo gateway. ALB mutual TLS (mTLS) in **verify mode** requires every client to
present an X.509 certificate that chains to a Certificate Authority (CA) in the
ALB's trust store. Zuplo presents that client certificate on each outbound
request, and the ALB rejects anything that can't.

This guide covers the Zuplo side of that connection: uploading a client
certificate and presenting it on requests to the ALB. It does **not** cover
configuring the ALB itself — for that, follow the AWS documentation linked in
[Configure the ALB](#1-configure-the-alb).

## How it works

The ALB's HTTPS listener is configured for mTLS verify mode and backed by a
trust store that contains the CA which issued Zuplo's client certificate. On
each request, the gateway presents its client certificate, the ALB verifies it
against the trust store, and only then forwards the request to the target group.

<Diagram height="h-64">
  <DiagramNode id="client">Client</DiagramNode>
  <DiagramNode id="gateway" variant="zuplo">
    Zuplo Gateway
  </DiagramNode>
  <DiagramGroup id="aws" label="AWS VPC">
    <DiagramNode id="alb" variant="orange">
      ALB (mTLS verify)
    </DiagramNode>
    <DiagramNode id="backend" variant="green">
      Backend targets
    </DiagramNode>
  </DiagramGroup>
  <DiagramEdge from="client" to="gateway" label="HTTPS" />
  <DiagramEdge from="gateway" to="alb" label="mTLS (client cert)" />
  <DiagramEdge from="alb" to="backend" label="Forwarded" />
</Diagram>

This gives you two guarantees at once:

- **The ALB trusts the gateway.** The load balancer rejects requests that don't
  present a valid client certificate before they reach your application.
- **The gateway trusts the ALB.** Standard TLS still verifies the ALB's server
  certificate, so the gateway knows it's talking to the real backend.

For background on the gateway-to-origin direction in general, see
[Gateway to Origin mTLS Authentication](./securing-backend-mtls.mdx).

## Prerequisites

Before you begin, you need:

- A client certificate and private key (PEM-encoded) issued by a CA. The same CA
  must be uploaded to the ALB's trust store.
- An AWS Application Load Balancer with an HTTPS listener you can configure for
  mTLS.
- The [Zuplo CLI](../cli/overview.mdx) installed and authenticated.

## 1/ Configure the ALB

On the AWS side, configure the ALB's HTTPS listener to use mutual TLS in
**verify mode** and create a trust store that contains the CA which signed your
client certificate. Verify mode is what makes the ALB perform X.509 client
certificate authentication during the TLS handshake.

Follow the AWS documentation:

- [Mutual authentication with TLS in Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html)
  — concepts, verify vs. passthrough mode, and trust stores.
- [Configuring mutual TLS on an Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/configuring-mtls-with-elb.html)
  — step-by-step listener and trust store setup.

:::caution

Use **verify** mode, not passthrough. In passthrough mode the ALB forwards the
client certificate to your targets without checking it, so the load balancer
does not enforce trust. Verify mode is what rejects untrusted callers at the
edge.

:::

## 2/ Upload your client certificate to Zuplo

Use the Zuplo CLI to upload the client certificate and private key to your
project. The CA that issued this certificate must already be in the ALB's trust
store.

```bash
zuplo mtls-certificate create \
  --cert client-cert.pem \
  --key client-key.pem \
  --name aws-alb-cert \
  --account your-account \
  --project your-project \
  --environment-type development \
  --environment-type preview \
  --environment-type production
```

:::note

The certificate name must follow JavaScript's variable naming constraints
because you reference it by name in your configuration. The CLI validates this
when you create the certificate.

:::

## 3/ Present the certificate on requests to the ALB

Reference the uploaded certificate by name when the gateway forwards requests to
the ALB. Use the ALB's DNS name (or a custom domain pointed at it) as the
backend URL.

The simplest option is the [URL Forward Handler](../handlers/url-forward.mdx),
configured directly on a route in `config/routes.oas.json`:

```json
{
  "handler": {
    "export": "urlForwardHandler",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "baseUrl": "https://my-alb-1234567890.us-east-1.elb.amazonaws.com",
      "mtlsCertificate": "aws-alb-cert"
    }
  }
}
```

If you need to inspect or transform the request before forwarding, use a
[Function Handler](../handlers/custom-handler.mdx) and pass the certificate name
in the `zuplo` options of `fetch`:

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const response = await fetch(
    "https://my-alb-1234567890.us-east-1.elb.amazonaws.com/api",
    {
      zuplo: {
        mtlsCertificate: "aws-alb-cert",
      },
    },
  );

  return response;
}
```

## 4/ Use environment variables across environments

To use a different certificate per environment, store the certificate name in an
[environment variable](./environment-variables.mdx) and reference it with the
`$env()` selector:

```json
{
  "handler": {
    "export": "urlForwardHandler",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "baseUrl": "${env.ALB_BACKEND_URL}",
      "mtlsCertificate": "$env(ALB_MTLS_CERT)"
    }
  }
}
```

In a Function Handler, read the same variable from the `environment` object:

```ts
import { ZuploContext, ZuploRequest, environment } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const response = await fetch(`${environment.ALB_BACKEND_URL}/api`, {
    zuplo: {
      mtlsCertificate: environment.ALB_MTLS_CERT,
    },
  });

  return response;
}
```

## Verify the connection

Deploy your changes to a preview or production environment, then send a request
through the gateway to a route that forwards to the ALB. A successful response
confirms the ALB accepted the client certificate.

:::warning

mTLS bindings aren't available in local development. Code that references an
mTLS certificate only works once deployed to a Zuplo edge environment — test in
a preview environment rather than locally.

:::

## Troubleshooting

### Requests fail with a 522 or connection error

A `522` means the connection to the ALB failed before an HTTP response was
received — usually a TLS handshake problem. Confirm that:

- The CA that issued Zuplo's client certificate is present in the ALB's trust
  store.
- The client certificate hasn't expired.
- The `mtlsCertificate` name in your configuration matches the name you used in
  `zuplo mtls-certificate create`.

### The ALB rejects the certificate

If the handshake completes but the ALB returns a `403`, the certificate is being
presented but not trusted. Re-check the ALB trust store and confirm the listener
is in **verify** mode, not passthrough. If your client certificate is issued by
an intermediate CA, make sure the trust store contains the full chain back to
the root.

### No certificate appears to be sent

Confirm the upload succeeded and the certificate is enabled for the environment
you deployed to:

```bash
zuplo mtls-certificate list \
  --account your-account \
  --project your-project
```

## Related resources

- [Gateway to Origin mTLS Authentication](./securing-backend-mtls.mdx)
- [Securing your backend](./securing-your-backend.mdx)
- [URL Forward Handler](../handlers/url-forward.mdx)
- [Function Handler](../handlers/custom-handler.mdx)
- [Environment Variables](./environment-variables.mdx)
- [Mutual authentication with TLS in Application Load Balancer (AWS)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html)
