Skip to content
Self-Hosting

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 new API keys alongside the legacy API keys (ANON_KEY and SERVICE_ROLE_KEY HS256-signed JWTs).

Before you begin#

  • Complete the Docker setup guide so that JWT_SECRET, ANON_KEY, and SERVICE_ROLE_KEY are set in your .env file. Quick start (Linux) handles this automatically; the manual path runs generate-keys.sh.
  • If you are upgrading from a legacy self-hosted Supabase environment, make sure to check the changelog and add/update 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:

1
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.

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

docker-compose.yml
1
auth:
2
environment:
3
# JSON array of signing JWKs (EC private + legacy symmetric)
4
GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}
5
6
rest:
7
environment:
8
# PostgREST accepts a plain-text symmetric secret, a single JWK, or a JWKS.
9
PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}
10
11
realtime:
12
environment:
13
# JWKS for token verification (EC public + legacy symmetric)
14
API_JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}
15
16
storage:
17
environment:
18
# JWKS for token verification (EC public + legacy symmetric)
19
JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}
20
21
functions:
22
environment:
23
# JWKS for token verification (EC public + legacy symmetric).
24
SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}}

Restart all services:

1
sh run.sh recreate

New API keys format#

The new API keys use the same format as the Supabase platform:

1
sb_publishable_<22-char-random>_<8-char-checksum>
2
sb_secret_<22-char-random>_<8-char-checksum>

Verifying the setup#

Test with the new secret key:

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

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

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

Both should work and return the same result.

You can also verify the public JWKS endpoint:

1
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)TypeDescription
JWT_SECRETSymmetric secretExisting: Shared secret for signing and verifying HS256 JWTs. Used by multiple services.
ANON_KEYHS256 JWTExisting: Legacy client-side API key. Embedded JWT with role: "anon".
SERVICE_ROLE_KEYHS256 JWTExisting: Legacy server-side API key. Embedded JWT with role: "service_role".
SUPABASE_PUBLISHABLE_KEYOpaqueNew: Short random key with checksum. Replaces ANON_KEY for client-side use.
SUPABASE_SECRET_KEYOpaqueNew: Short random key with checksum. Replaces SERVICE_ROLE_KEY for server-side use.
JWT_KEYSJSON arrayNew: JSON array of signing JWKs containing the new asymmetric key pair and the legacy symmetric key. Used by Auth to sign tokens.
JWT_JWKSJWKS (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.

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:

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

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

1
sh run.sh recreate

Regenerating asymmetric key pair#

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

1
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.

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.

Additional resources#

On GitHub: