How to Configure Asymmetric JWTs on Self-Hosted Supabase
Source: Dev.to
Introduction
Self‑hosting Supabase gives you full control over your production environment, but the official documentation focuses on the hosted service. One feature that isn’t well covered for the open‑source version is asymmetric JWT signing (RSA or ECC). This guide walks through configuring asymmetric JWTs on a self‑hosted Supabase stack.
Prerequisites
- A running self‑hosted Supabase instance (docker‑compose).
- Access to the
docker‑compose.ymlfile for each Supabase service. - Optional: Dokploy for easier deployment (the example uses a Dokploy template).
Generate Asymmetric Keys
A small script creates a key pair, formats it as JWK, and prepares the environment variables you’ll need.
curl -sL https://gist.githubusercontent.com/vpcano/28e93b8af3cb36ba3ecd9a397ccf0ab7/raw/33a2769ac9d2ff0b8fa953566ee1fd5a773ec159/supabase-keygen.sh | bash
The script:
- Generates a public/private key pair (RS256 or ES256).
- Prompts for an optional Key ID (kid) or creates a random one.
- Writes the keys in JSON Web Key (JWK) format.
- Asks for your existing
ANON_KEYandSERVICE_ROLE_KEYto re‑sign them with the new keys.
The output provides the following environment variables.
Environment Variables
| Variable | Description | Supabase service / env var |
|---|---|---|
JWT_SIGNING_KEYS | Private + public keys (JWK) | GOTRUE_JWT_KEYS (auth service) |
JWT_METHODS | Enables asymmetric algorithms (e.g., RS256,ES256) | GOTRUE_JWT_VALID_METHODS (auth service) |
JWT_JWKS | Public key in JWK format | PGRST_JWT_SECRET, PGRST_APP_SETTINGS_JWT_SECRET (REST), API_JWT_JWKS (Realtime), JWT_JWKS (Storage) |
ANON_KEY / SERVICE_ROLE_KEY | Replace the previous keys on all services and client apps | – |
Add these variables to the appropriate sections of your docker‑compose.yml, for example:
services:
auth:
environment:
- GOTRUE_JWT_KEYS=${JWT_SIGNING_KEYS}
- GOTRUE_JWT_VALID_METHODS=${JWT_METHODS}
rest:
environment:
- PGRST_JWT_SECRET=${JWT_JWKS}
- PGRST_APP_SETTINGS_JWT_SECRET=${JWT_JWKS}
realtime:
environment:
- API_JWT_JWKS=${JWT_JWKS}
storage:
environment:
- JWT_JWKS=${JWT_JWKS}
Expose the JWKS Endpoint via Kong
The public JWKS endpoint (/auth/v1/.well-known/jwks.json) must be reachable without authentication. Edit the Kong configuration file (kong.yml) that is mounted as a volume in the Kong container.
services:
## Open Auth routes
- name: auth-v1-open-jwks
url: http://auth:9999/.well-known/jwks.json
routes:
- name: auth-v1-open-jwks
strip_path: true
paths:
- /auth/v1/.well-known/jwks.json
plugins:
- name: cors
## Secure Auth routes
# ... (other services)
After updating kong.yml, restart all Supabase containers (or run docker compose up -d --force-recreate).
Verify the Setup
-
Check the JWKS endpoint
Visit
https:///auth/v1/.well-known/jwks.json. You should see JSON similar to:{ "keys": [ { "alg": "ES256", "crv": "P-256", "key_ops": ["verify"], "kid": "...", "kty": "EC", "use": "sig", "x": "...", "y": "..." } ] } -
Inspect a user JWT
-
Extract a JWT from an HTTP header or cookie.
-
Decode it at .
-
The header should contain:
{ "alg": "ES256", // or "RS256" "typ": "JWT", "kid": "..." }
-
-
Signature verification
Paste the public key (the JWKS JSON) into the “JWT Signature Verification” section on jwt.io to confirm the signature validates correctly.
Conclusion
You’ve now configured asymmetric JWT signing for a self‑hosted Supabase deployment. This improves security (private key never leaves the server) and can reduce latency because clients can verify tokens locally using the cached JWKS.
Feel free to adapt the script or Kong configuration to match your infrastructure. Happy self‑hosting!