Next.js

Fix next-auth /api/auth/error in production: NO_SECRET

Reproduced on next-auth 4.24.15 and Next.js 15.5.28: why sign-in works on localhost but fails after deploy, what a 404 versus a 500 at /api/auth/error tells you, and the env change that fixes it.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
8 min read

TL;DR#

  • Cause: next-auth v4 needs a secret in production. In development a missing secret is only a warning; under next start the configuration check fails, every auth endpoint answers with a 500, and the client signIn() sends the browser to /api/auth/error.
  • Fix: add NEXTAUTH_SECRET (or its alias AUTH_SECRET) to the Production environment of your host, then trigger a new deployment.
  • Also check: NEXTAUTH_URL must be your real domain (or absent on Vercel), and Google's authorised redirect URI must use that same domain.
  • Reproduced on next-auth 4.24.15, Next.js 15.5.28 and Node 22.23.3 (Debian 12 container), 19 October 2026.

The symptom, and what the server log says#

The Stack Overflow question (78,000 views) describes the classic shape: Google sign-in works on http://localhost:3000, but on the Vercel deployment the sign-in button lands on https://example.vercel.app/api/auth/error. The browser only shows that URL. The real reason is in the server log, which is the first place to look. With no secret configured, a production build of next-auth 4.24.15 prints this on every auth request:

text
[next-auth][error][NO_SECRET] 
https://next-auth.js.org/errors#no_secret Please define a `secret` in production. Error [MissingSecretError]: Please define a `secret` in production.
    at async Module.s (.next/server/pages/api/auth/[...nextauth].js:1:10197) {
  code: 'NO_SECRET'
}

On Vercel this appears in the function logs of the deployment. The JSON endpoint the client calls first answers with a 500:

text
--- curl -s -i http://localhost:3000/api/auth/providers
HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
Content-Length: 107
 
{"message":"There is a problem with the server configuration. Check the server logs for more information."}

The question itself dates from June 2021 and uses the pre-v4 import Providers.Google. (Date and connection headers are trimmed from the curl output in this article.) Its exact 404 page was not reproduced on current versions; the section on 404 versus 500 below explains what each one means today.

Why it happens only in production#

The check lives in assert.ts. In the installed 4.24.15 package it reads:

text
  if (!warned) {
    if (!req.origin) warnings.push("NEXTAUTH_URL");
    if (!options.secret && process.env.NODE_ENV !== "production") warnings.push("NO_SECRET");
    if (options.debug) warnings.push("DEBUG_ENABLED");
  }
  if (!options.secret && process.env.NODE_ENV === "production") {
    return new _errors.MissingSecret("Please define a `secret` in production.");
  }

So the same configuration gives a warning under next dev and an error under next start. The options documentation says it in plain words: "Not providing any secret or NEXTAUTH_SECRET will throw an error in production." In development, the docs add, next-auth falls back to a hash of the configuration options, which is why localhost never complained.

When that check fails, the request handler in core/index.ts logs the error and stops. For a GET to the HTML pages (signin, signout, error, verify-request) it renders the built-in "Server error" page; for everything else, including /api/auth/providers, it returns the 500 JSON shown above.

The redirect to /api/auth/error comes from the browser side. In the installed client code, signIn() first fetches the providers list; a non-OK response is caught, logged as CLIENT_FETCH_ERROR and turned into null, and then:

text
          providers = _context6.sent;
          if (providers) {
            _context6.next = 8;
            break;
          }
          window.location.href = "".concat(baseUrl, "/error");

That client path was read in the installed source, not clicked through in a browser; the server half (the 500 from /api/auth/providers) was observed directly.

404 or 500 at /api/auth/error: two different problems#

The status of /api/auth/error itself tells you which problem you have.

With the route deployed and no secret, the page exists but fails:

text
--- curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/api/auth/error
HTTP 500

With the pages/api/auth/[...nextauth].js file missing from the build (secret set), the same URL is a plain Next.js 404:

text
--- curl -s -o /tmp/error.html -w 'HTTP %{http_code}' http://localhost:3000/api/auth/error
HTTP 404

and the page body contains the "This page could not be found" text from the question. A 404 therefore points at whatever serves that URL: when it is your own app, its build has no route for /api/auth/* (the catch-all was not deployed, sits in the wrong folder, or is misnamed). Check the host in the address bar too. An explicit NEXTAUTH_URL sets the host of every auth URL next-auth builds (see step 4), so a placeholder value can send the browser to /api/auth/error on a domain that is not your app. A 500 with "Server error" means the route runs and rejects its configuration, and the server log names the reason. In a project using the App Router, the route is a Route Handler instead; if next build rejects it, see the fix for 'authOptions' is not a valid Route export field.

Fix#

  1. Generate a random secret. The next-auth deployment guide suggests OpenSSL:

    bash
    openssl rand -base64 32
  2. Add it to the production environment as NEXTAUTH_SECRET. On Vercel, add it on the project's Environment Variables settings page and select the Production environment (Vercel: managing environment variables); the next-auth guide recommends all environments. The variable is read on the server, so it needs no NEXT_PUBLIC_ prefix. AUTH_SECRET works too; the options docs call it "an alias, and is the preferred naming going forward", and the reproduction below confirms it. On a host other than Vercel, set the variable in the environment of the process that runs next start.

  3. Redeploy. Adding the variable does not change the deployment that is already live. The Vercel documentation states: "Any change you make to environment variables are not applied to previous deployments, they only apply to new deployments." The same rule bites NEXT_PUBLIC_ values, covered in Next.js env variables undefined on Vercel.

  4. Fix NEXTAUTH_URL if you set it. The top-voted answer (score 15, not accepted) points out that https://example.vercel.app in the docs is literally an example. In the installed source, an explicit NEXTAUTH_URL always wins over host detection:

    text
    if (process.env.NEXTAUTH_URL) return process.env.NEXTAUTH_URL;

    So a wrong value is used as-is for every callback URL. Either set it to your own domain, or remove it on Vercel: the deployment guide says "You do not need the NEXTAUTH_URL environment variable in Vercel." That only holds when the project exposes Vercel's system environment variables: the options docs ask you to make sure Automatically expose System Environment Variables is checked, and the installed source shows why. Without VERCEL (or AUTH_TRUST_HOST) in the environment, the detected origin is undefined and next-auth falls back to http://localhost:3000. On other hosts you do need NEXTAUTH_URL.

  5. Match the Google redirect URI. In Google Cloud Console, the authorised redirect URI must be your production domain followed by /api/auth/callback/google. Google's OAuth documentation describes the error you get otherwise, redirect_uri_mismatch: "The redirect_uri passed in the authorization request does not match an authorized redirect URI for the OAuth client ID." This part is the vendor's console and was not reproduced here.

Verify the fix#

After the new deployment, call the providers endpoint on your domain. With NEXTAUTH_SECRET set as the only change, the reproduction went from 500 to:

text
--- curl -s -i http://localhost:3000/api/auth/providers
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 179
 
{"google":{"id":"google","name":"Google","type":"oauth","signinUrl":"http://localhost:3000/api/auth/signin/google","callbackUrl":"http://localhost:3000/api/auth/callback/google"}}

/api/auth/signin and /api/auth/error both returned 200, and the server log held no NO_SECRET line. The callbackUrl in that JSON is the exact string to paste into Google Cloud Console: if it shows the wrong host, step 4 is not done.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Where next-auth looks for the secret, tested#

The question's config hides its secret under session.jwt. On 4.24.15 the Pages Router handler (pages/api/auth/[...nextauth]) fills the secret in this order: the top-level secret option, then jwt.secret, then NEXTAUTH_SECRET, then AUTH_SECRET; the App Router route handler and getServerSession read only the top-level option, then NEXTAUTH_SECRET, then AUTH_SECRET (see next/index.ts). Three placements were run under next start, none of them with NEXTAUTH_SECRET set (the first two are separate builds with the secret written in the config file):

Placement/api/auth/providers
session.jwt.secret (the question's shape)500, NO_SECRET logged
top-level secret option200
AUTH_SECRET env variable only200

So the answer that puts a top-level secret in the config works, but its literal string commits the secret to the repository. Setting NEXTAUTH_SECRET in the host's environment gives the same result without the secret ever reaching git.

A path in NEXTAUTH_URL moves every auth URL#

The question also tried NEXTAUTH_URL values ending in /api/auth and /api/auth/signin. next-auth takes the path of NEXTAUTH_URL as its base path, falling back to /api/auth only when the path is /. A value ending in /api/auth is therefore harmless. A value ending in /api/auth/signin is not:

text
{"google":{"id":"google","name":"Google","type":"oauth","signinUrl":"http://localhost:3000/api/auth/signin/signin/google","callbackUrl":"http://localhost:3000/api/auth/signin/callback/google"}}

That callback no longer matches the URI registered with Google. Keep NEXTAUTH_URL at the bare origin unless your app really serves auth under a custom base path.

On Auth.js v5#

This reproduction covers v4 only. In the Auth.js core source on the main branch, the missing-secret check has no NODE_ENV condition, unlike the v4 check above (not tested here), and env.ts reads AUTH_SECRET first, then NEXTAUTH_SECRET. How a v5 beta reports a failed credentials login, measured against v4, is in NextAuth login failed: handle CredentialsSignin.

Once sign-in works, a common next production surprise is middleware: if protected routes still redirect to the login page, the middleware may be reading a different secret than the auth route, which is one of the causes in Next.js middleware not running on Vercel. Separately, if next build on an older Next.js release refuses to compile pageProps.session in _app.tsx, that type error has its own page: NextAuth pageProps.session does not exist on the props type.

Further reading: Fix /tmp/chromium libnss3.so Error on Vercel and Lambda. Also see Fix Auth.js UntrustedHost in Production.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote