# New API Keys and Asymmetric Authentication

Configure new API keys and ES256 asymmetric authentication for self-hosted Supabase.

You can configure self-hosted Supabase to use the [publishable and secret API keys](/docs/guides/getting-started/api-keys) alongside the legacy API keys (`ANON_KEY` and `SERVICE_ROLE_KEY` HS256-signed JWTs).

## Before you begin

- Complete the [Docker setup guide](/docs/guides/self-hosting/docker) so that `JWT_SECRET`, `ANON_KEY`, and `SERVICE_ROLE_KEY` are set in your `.env` file. [Quick start (Linux)](/docs/guides/self-hosting/docker#quick-start-linux) handles this automatically; the manual path runs [`generate-keys.sh`](/docs/guides/self-hosting/docker#generate-keys-and-secrets).
- If you are upgrading from a legacy self-hosted Supabase environment, make sure to check the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md#2026-03-16) and [add/update](/docs/guides/self-hosting/updating) the following files:
  - `.env.example` (merge new sections into your `.env` file)
  - `docker-compose.yml`
  - `utils/add-new-auth-keys.sh`
  - `utils/rotate-new-api-keys.sh`
  - `volumes/api/envoy/*`

## Adding the new keys

From your project directory where you have `docker-compose.yml`:

```sh
sh utils/add-new-auth-keys.sh --update-env
```

This generates new configuration environment variables and writes them to `.env`. Omit `--update-env` to review and confirm the changes interactively.

Caution: The script reads `JWT_SECRET` from `.env` and includes it as a symmetric key inside both `JWT_KEYS` and `JWT_JWKS`. If you later change `JWT_SECRET`, you must regenerate the JWKS as well.

In addition, the following configuration is uncommented automatically by the script for the new authentication to work correctly:

```yaml name=docker-compose.yml
auth:
  environment:
    # JSON array of signing JWKs (EC private + legacy symmetric)
    GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}

rest:
  environment:
    # PostgREST accepts a plain-text symmetric secret, a single JWK, or a JWKS.
    PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}

realtime:
  environment:
    # JWKS for token verification (EC public + legacy symmetric)
    API_JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}

storage:
  environment:
    # JWKS for token verification (EC public + legacy symmetric)
    JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}

functions:
  environment:
    # JWKS for token verification (EC public + legacy symmetric).
    SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}}
```

Caution: Nested variable interpolation (`${A:-${B}}`) requires `podman-compose >= 1.6.0`. Earlier versions (still shipped by some Linux distributions) do not support it - if you are on an older `podman-compose`, either upgrade or replace each nested expression with the required variable directly, see the inline comments in `docker-compose.yml` for the exact substitutions.

Restart all services:

```sh
sh run.sh recreate
```

### New API keys format

These keys use the same format as [API keys](/docs/guides/getting-started/api-keys) on the Supabase platform:

```
sb_publishable_<22-char-random>_<8-char-checksum>
sb_secret_<22-char-random>_<8-char-checksum>
```

### Verifying the setup

Test with the new secret key:

```sh
curl http://<your-domain>/rest/v1/ \
-H "apikey: your-supabase-secret-key"
```

You should receive a valid response from PostgREST. Then verify that the legacy service role key still works:

```sh
curl http://<your-domain>/rest/v1/ \
-H "apikey: your-service-role-key"
```

Both should work and return the same result.

Note: `/rest/v1/` is the Open API root and requires admin-level keys (`sb_secret_*` or legacy `SERVICE_ROLE_KEY`). Public keys (`sb_publishable_*` or legacy `ANON_KEY`) return `403` for this endpoint.

You can also verify the public JWKS endpoint:

```sh
curl http://<your-domain>/auth/v1/.well-known/jwks.json
```

This should return the EC public key (the symmetric key is excluded). Third-party services can use this endpoint to obtain the public key and verify asymmetric user session JWTs without needing the private key.

### Environment variables configuration

New variables default to empty values in `.env.example`. When empty, the API gateway and all services operate in legacy-only mode: `sb_publishable` and `sb_secret` API keys are not configured.

| Environment variable (existing and new) | Type             | Description                                                                                                                           |
| --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_SECRET`                            | Symmetric secret | **Existing:** Shared secret for signing and verifying HS256 JWTs. Used by multiple services.                                          |
| `ANON_KEY`                              | HS256 JWT        | **Existing:** Legacy client-side API key. Embedded JWT with `role: "anon"`.                                                           |
| `SERVICE_ROLE_KEY`                      | HS256 JWT        | **Existing:** Legacy server-side API key. Embedded JWT with `role: "service_role"`.                                                   |
| `SUPABASE_PUBLISHABLE_KEY`              | Opaque           | **New:** Short random key with checksum. Replaces `ANON_KEY` for **client-side** use.                                                 |
| `SUPABASE_SECRET_KEY`                   | Opaque           | **New:** Short random key with checksum. Replaces `SERVICE_ROLE_KEY` for **server-side** use.                                         |
| `JWT_KEYS`                              | JSON array       | **New:** JSON array of signing JWKs containing the new asymmetric key pair and the legacy symmetric key. Used by Auth to sign tokens. |
| `JWT_JWKS`                              | JWKS (JSON)      | **New:** Contains the new public key and the legacy symmetric key. Used by PostgREST, Realtime, Storage, and Functions.               |

### Differences from the Supabase platform

- **One key per role.** Self-hosted Supabase supports a single `sb_publishable` and a single `sb_secret`. The platform allows creating multiple `sb_` keys per project.
- **No checksum validation.** The opaque keys use the same format as the platform (`sb_publishable_<random>_<checksum>`), but the API gateway does not validate the checksum. Keys are matched as opaque strings by the API gateway.

### Backward compatibility

The new authentication configuration is fully backward compatible:

- **The API Gateway accepts both key types simultaneously.** You can migrate clients incrementally - some using legacy API keys, others using the new ones.
- **JWKS includes the symmetric key.** `JWT_JWKS` contains both the EC public key (for verifying new ES256 tokens) and the legacy `JWT_SECRET` as a symmetric JWK (for verifying old HS256 tokens). Services that receive `JWT_JWKS` can verify both token types.
- **No database changes required.** The asymmetric key system operates entirely at the API gateway and service configuration layer.

Caution: When `JWT_KEYS` is set, Auth will start signing new user session JWTs with the new asymmetric ES256 key pair. Make sure all services that verify tokens (PostgREST, Realtime, Storage) are configured with `JWT_JWKS` so they can verify both the new ES256 and legacy HS256 tokens.

## Rotating the new API keys

If your new API keys are compromised or you want to rotate them periodically, you can regenerate `sb_publishable` and `sb_secret` without touching the asymmetric key pair:

```sh
sh utils/rotate-new-api-keys.sh --update-env
```

After rotating, restart services and update your client applications with the new keys:

```sh
sh run.sh recreate
```

Note: Rotating new API keys does not invalidate existing user sessions. User session JWTs issued by Auth are unaffected because they are verified using the asymmetric key pair, which remains unchanged.

## Regenerating asymmetric key pair

If the EC private key is compromised or you need to regenerate everything:

```sh
sh utils/add-new-auth-keys.sh --update-env
```

This generates a new EC P-256 key pair, new JWKS, new asymmetric JWTs, and new `sb_` API keys. After updating `.env` and restarting services:

- New user session tokens will be signed with the new EC key.
- Existing user session tokens signed with the old EC key will fail verification. Users will need to sign in again.
- Existing user session tokens signed with the legacy symmetric key (`JWT_SECRET`) will continue to work, since `JWT_SECRET` hasn't changed and is still included in the new JWKS.

Danger: Regenerating asymmetric keys invalidates all ES256 user sessions. Plan a maintenance window if your users have active sessions.

## How it works

Requests via `supabase-js` include two headers for every service except Edge Functions:

- `apikey` - the API key (`sb_` or legacy JWT)
- `Authorization` - when unauthenticated, the client SDK copies the API key here (`Bearer sb_publishable_xxx` or `Bearer eyJ...`). When authenticated, this contains the user session JWT minted by Auth.

For **Realtime WebSocket** connections, the API key is sent as a `?apikey=` query parameter in the upgrade URL instead of an `apikey` header.

**Storage** and **Edge Functions** also accept requests without an API key. These services handle their own authentication.

For details on how the API gateway routes and authenticates requests, see the [Envoy API Gateway guide](/docs/guides/self-hosting/self-hosted-envoy#authentication).

## Additional resources

- [API keys](/docs/guides/getting-started/api-keys) - How API keys work on the Supabase platform
- [Auth architecture](/docs/guides/auth/architecture) - How the Auth service handles authentication and token signing
- [JWT Signing Keys](/docs/guides/auth/signing-keys) - Best practices on managing keys used by Supabase Auth to create and verify JSON Web Tokens
- [JSON Web Token (JWT)](/docs/guides/auth/jwts) - How to best use JSON Web Tokens with Supabase
- [Self-hosting with Docker](/docs/guides/self-hosting/docker) - Initial setup guide, including legacy key generation

On GitHub:

- [Upcoming changes to Supabase API Keys (Discussion #29260)](https://github.com/orgs/supabase/discussions/29260)
- [Supabase Auth: Asymmetric Keys support (Discussion #29289)](https://github.com/orgs/supabase/discussions/29289)
- [Self-hosted Supabase: Envoy becomes the default API gateway (Discussion #48048)](https://github.com/orgs/supabase/discussions/48048)
