If your product is aimed at developers, GitHub login is the sign-in button people actually click. It's one click, there's no password to remember, and nearly everyone in your audience already has an account. On Supabase Cloud, enabling it takes about two minutes in the dashboard. On self-hosted Supabase, you'll discover the Auth provider panel in Studio is disabled entirely — everything has to be configured through environment variables and a container restart.
The configuration itself isn't hard once you know where things go. The pain is in the details: which callback URL GitHub expects, which of the five similar-sounding GoTrue variables actually matter, and why users end up redirected to localhost:3000 after logging in on your production domain. This guide walks through the whole setup — GitHub OAuth app, Docker configuration, client code, and the errors you're most likely to hit.
How GitHub OAuth Flows Through a Self-Hosted Stack
Before touching config, it helps to know what happens when someone clicks "Sign in with GitHub":
- Your app calls
signInWithOAuth({ provider: 'github' }), which redirects the browser to your Supabase Auth endpoint (/auth/v1/authorize?provider=github). - GoTrue (the Auth service) redirects to GitHub's authorization page.
- The user approves, and GitHub redirects back to your callback URL:
https://your-api-domain/auth/v1/callback. - GoTrue exchanges the code for a GitHub access token, fetches the user's profile and email, creates or matches a user in
auth.users, and finally redirects to your app'sSITE_URL(or an allowedredirectTo) with a session.
Two things follow from this. First, GitHub talks directly to your server in step 3, so your Supabase API must be reachable on a public HTTPS URL — a custom domain with valid SSL is effectively a prerequisite for production. Second, there are two redirect URLs in play: the callback GitHub sends users to (your API), and the final redirect GoTrue sends users to (your app). Mixing these up causes most of the failures you'll see.
Step 1: Create the GitHub OAuth App
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App (or create it under an organization if the app belongs to your company).
Fill in:
- Application name: what users see on the consent screen — use your product name, not "supabase-test"
- Homepage URL:
https://yourapp.com - Authorization callback URL:
https://api.yourapp.com/auth/v1/callback
The callback URL must point at your Supabase API domain, not your frontend. If your instance serves everything from one domain, it's that domain plus /auth/v1/callback. GitHub matches this URL strictly — http vs https, a trailing slash, or a www mismatch will produce a redirect_uri_mismatch error.
After creating the app, copy the Client ID, then click Generate a new client secret and copy it immediately — GitHub only shows it once.
One honest note on OAuth Apps vs GitHub Apps: GitHub nudges you toward the newer "GitHub Apps" for most integrations, but for plain sign-in with GoTrue, a classic OAuth App is the simpler, well-trodden path. You don't need webhooks, installation flows, or fine-grained repo permissions just to authenticate users.
Step 2: Configure GoTrue in Docker Compose
On a self-hosted instance, GitHub login is enabled with four environment variables on the auth service. Add them to your .env file:
# .env GOTRUE_EXTERNAL_GITHUB_ENABLED=true GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=your-client-id GOTRUE_EXTERNAL_GITHUB_SECRET=your-client-secret GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=https://api.yourapp.com/auth/v1/callback
Then make sure they're passed through in docker-compose.yml (the stock Supabase compose file doesn't forward provider variables it doesn't know about):
auth:
environment:
GOTRUE_EXTERNAL_GITHUB_ENABLED: ${GOTRUE_EXTERNAL_GITHUB_ENABLED}
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID: ${GOTRUE_EXTERNAL_GITHUB_CLIENT_ID}
GOTRUE_EXTERNAL_GITHUB_SECRET: ${GOTRUE_EXTERNAL_GITHUB_SECRET}
GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI}
While you're in the file, verify the three URL variables that OAuth depends on:
API_EXTERNAL_URL=https://api.yourapp.com GOTRUE_SITE_URL=https://yourapp.com GOTRUE_URI_ALLOW_LIST=https://yourapp.com/**,http://localhost:3000/**
API_EXTERNAL_URLis the public URL of your Supabase API — GoTrue uses it to build the callback it hands to GitHub. If this still sayshttp://localhost:8000, GitHub login cannot work from the outside world. Note that Supabase's mid-2026 Docker defaults changed how this value is prefixed, so after an upgrade, double-check that your callback still resolves to/auth/v1/callback.GOTRUE_SITE_URLis where users land after a successful login by default.GOTRUE_URI_ALLOW_LISTwhitelists any otherredirectTotargets (your local dev URL, preview deploys, deep links).
Apply the changes by recreating the container — a plain restart won't pick up new environment variables:
docker compose up -d auth
Confirm it took:
docker compose exec auth env | grep GITHUB
Step 3: Trigger the Flow From Your App
Client-side, GitHub login is one call with supabase-js:
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://yourapp.com/auth/callback',
},
});
If you skip redirectTo, users land on GOTRUE_SITE_URL. If you set it, the value must match a pattern in GOTRUE_URI_ALLOW_LIST — otherwise GoTrue silently falls back to the site URL, which looks like a bug but is the allow-list doing its job.
By default GoTrue requests GitHub's user:email scope, so you'll get the user's primary email even when their email is set to private on GitHub. The email arrives verified (GitHub has already verified it), so no confirmation email is sent for OAuth signups.
Common Errors and What They Actually Mean
redirect_uri_mismatch on GitHub's page. The callback URL registered in the GitHub OAuth app doesn't exactly match what GoTrue sent. Compare character by character: scheme, host, port, path. This is the single most common failure, and the long-running self-hosted OAuth discussion on GitHub is full of it.
"Unsupported provider: provider is not enabled". GoTrue never saw GOTRUE_EXTERNAL_GITHUB_ENABLED=true. Either the variable isn't forwarded in docker-compose.yml, or the container wasn't recreated. Run the env | grep GITHUB check above.
Login works, but users are redirected to localhost:3000. GOTRUE_SITE_URL is still set to its default. This one is embarrassing in production, and it's a config problem, not a code problem.
Works in production, broken in local dev (or vice versa). GitHub OAuth apps support exactly one callback URL. The standard fix is a second OAuth app ("MyApp Dev") pointing at http://localhost:8000/auth/v1/callback — localhost is the one place GitHub accepts plain HTTP.
Certificate errors during the callback. GitHub won't complete the exchange against a self-signed certificate. If you're mid-setup on SSL, sort that first — our guide to troubleshooting SSL certificate errors covers the usual suspects.
Also plan for one operational reality: client secret rotation. When you regenerate the secret on GitHub, you must update .env and recreate the auth container in the same window, or logins fail for everyone until you do. Put it in your runbook rather than discovering it live.
The Config-File Treadmill (and a Way Off It)
None of the above is intellectually difficult. But notice what it involves: hand-editing .env, patching docker-compose.yml, recreating containers over SSH, and repeating all of it for every provider and every project. Add Google and Apple and you're maintaining a dozen provider variables per instance, with no UI to tell you what's currently enabled.
This is exactly the kind of toil Supascale removes. It gives self-hosted instances the provider configuration UI that Studio lacks: enable GitHub, paste the client ID and secret, and Supascale writes the GoTrue configuration and restarts the service for you — the auth providers docs show the full flow. The same panel handles Google, Discord, Apple, and the rest, per project, across every instance you manage. It's a one-time $39.99 purchase for unlimited projects, so the math works even for a single side project — see pricing for details.
Self-hosting means owning this layer either way; the question is whether you manage it with a UI and an API or with SSH sessions and tribal knowledge.
Wrapping Up
GitHub login on self-hosted Supabase comes down to five things: an OAuth app with the exact callback URL https://your-api-domain/auth/v1/callback, four GOTRUE_EXTERNAL_GITHUB_* variables, correct API_EXTERNAL_URL and GOTRUE_SITE_URL values, an allow-list entry for any custom redirectTo, and a container recreate to apply it all. Get those right and the flow is solid; get one wrong and you'll meet one of the errors above.
If you'd rather configure it from a dashboard than a compose file, give Supascale a try — OAuth setup is a form, not a deployment.
