You deployed your Keystatic CMS to production, clicked “Sign in with GitHub,” and hit this error:
Be careful!
The redirect_uri is not associated with this application.
redirect_uri=https://localhost/api/keystatic/github/oauth/callback
Your production URL is in the browser, but GitHub OAuth is trying to redirect to localhost. What’s going on?
The Problem
When you set up Keystatic with GitHub authentication and deploy to cloud platforms, the OAuth flow breaks. Instead of using your actual domain (like https://yoursite.com), Keystatic constructs the callback URL with https://localhost, which obviously isn’t registered in your GitHub App settings.
This happens only in production — everything works fine locally during development.
Quick Debugging Checklist
Before diving into the fix, verify these basics:
- ✓ GitHub App callback URL is correct: Check that
https://yoursite.com/api/keystatic/github/oauth/callbackis registered in your GitHub App settings (notlocalhost) - ✓ Using GitHub App, not OAuth App: Keystatic requires a GitHub App, not the older OAuth App type
- ✓ Environment variables are set:
KEYSTATIC_GITHUB_CLIENT_ID,KEYSTATIC_GITHUB_CLIENT_SECRET, andKEYSTATIC_SECRETare configured in your hosting platform - ✓ Issue only happens in production: If localhost development works fine, this confirms the issue
If all these check out and you’re still seeing the error, you’ve hit the known Keystatic proxy header bug.
Root Cause
Modern cloud platforms like Vercel, Netlify, Cloudflare Pages, and Railway don’t serve your application directly. They use this architecture:
User Request → Load Balancer/Proxy → Your App (Internal Server)
yoursite.com (public) localhost:3000 (internal)
When requests arrive at your app, they’re coming from an internal proxy. The actual request URL shows http://localhost:3000, but the platform sets special headers to tell you the real public URL:
x-forwarded-host: yoursite.com(the actual domain)x-forwarded-proto: https(the actual protocol)
The bug: Keystatic doesn’t read these forwarded headers. It uses the internal hostname (localhost) when building OAuth redirect URLs, which breaks authentication.
This is a known issue in Keystatic that hasn’t been fixed yet.
The Solution
Create an Astro middleware that intercepts OAuth requests and rewrites the URL using the correct public domain from forwarded headers.
Step 1: Create src/middleware.ts in your Astro project:
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
// Fix Keystatic redirect_uri by respecting forwarded headers
// Workaround for https://github.com/Thinkmill/keystatic/issues/1022
const isOAuthRoute =
context.url.pathname.includes('/github/oauth/') ||
context.url.pathname.includes('/github/login');
if (isOAuthRoute) {
const forwardedHost = context.request.headers.get('x-forwarded-host');
const forwardedProto = context.request.headers.get('x-forwarded-proto');
if (forwardedHost && forwardedProto) {
// Rewrite URL to use the correct public domain
const correctUrl = new URL(context.url);
correctUrl.protocol = forwardedProto;
correctUrl.host = forwardedHost;
// Create new request with corrected URL
const newRequest = new Request(correctUrl.toString(), {
method: context.request.method,
headers: context.request.headers,
body: context.request.body,
// @ts-ignore
duplex: 'half'
});
// Update context with corrected values
Object.defineProperty(context, 'url', {
value: correctUrl,
writable: false
});
Object.defineProperty(context, 'request', {
value: newRequest,
writable: false
});
}
}
return next();
});
Step 2: After deploying, test the fix:
- Clear your browser cache or use incognito mode
- Navigate to
https://yoursite.com/keystatic - Click “Sign in with GitHub”
The redirect_uri should now use your production domain instead of localhost.
Why This Works
The middleware checks if the incoming request is OAuth-related (/github/oauth/ or /github/login). For these requests only, it reads the x-forwarded-host and x-forwarded-proto headers and rebuilds the request URL using the real public domain.
This way:
- OAuth flows see the correct public URL:
https://yoursite.com - Other Keystatic routes (like API proxying) work normally
- No changes needed to Keystatic configuration itself
Important Notes
This affects all proxy-based platforms, not just Vercel:
- Vercel
- Netlify
- Cloudflare Pages
- Fly.io
- Railway
- Render
- AWS CloudFront/ALB
You don’t need this fix if you’re hosting on:
- Traditional VPS (DigitalOcean, Linode, etc.) where your app directly serves the domain
- Localhost development
For other frameworks (Next.js, SvelteKit, etc.): The same concept applies, but you’ll need to adapt the middleware syntax for your framework’s middleware API.
References
- Keystatic GitHub Issue #1022 - Original bug report
- Keystatic GitHub Mode Documentation
- Keystatic + Astro Setup Guide
This middleware is a temporary workaround until Keystatic officially fixes the proxy header handling. Once they release a fix, you can remove this middleware and upgrade Keystatic.
Fixed it? This workaround has helped dozens of developers get Keystatic working in production. If you found this useful, consider sharing it with others who might be stuck on the same issue.