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/callback is registered in your GitHub App settings (not localhost)
  • 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, and KEYSTATIC_SECRET are 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:

  1. Clear your browser cache or use incognito mode
  2. Navigate to https://yoursite.com/keystatic
  3. 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

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.