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.
TL;DR#
- Cause: next-auth v4 needs a
secretin production. In development a missing secret is only a warning; undernext startthe configuration check fails, every auth endpoint answers with a 500, and the clientsignIn()sends the browser to/api/auth/error. - Fix: add
NEXTAUTH_SECRET(or its aliasAUTH_SECRET) to the Production environment of your host, then trigger a new deployment. - Also check:
NEXTAUTH_URLmust 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:
[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:
--- 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:
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:
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:
--- curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/api/auth/error
HTTP 500With the pages/api/auth/[...nextauth].js file missing from the build (secret set), the same URL is a plain Next.js 404:
--- curl -s -o /tmp/error.html -w 'HTTP %{http_code}' http://localhost:3000/api/auth/error
HTTP 404and 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#
-
Generate a random secret. The next-auth deployment guide suggests OpenSSL:
bashopenssl rand -base64 32 -
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 noNEXT_PUBLIC_prefix.AUTH_SECRETworks 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 runsnext start. -
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. -
Fix
NEXTAUTH_URLif you set it. The top-voted answer (score 15, not accepted) points out thathttps://example.vercel.appin the docs is literally an example. In the installed source, an explicitNEXTAUTH_URLalways wins over host detection:textif (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_URLenvironment 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. WithoutVERCEL(orAUTH_TRUST_HOST) in the environment, the detected origin is undefined and next-auth falls back tohttp://localhost:3000. On other hosts you do needNEXTAUTH_URL. -
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:
--- 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 option | 200 |
AUTH_SECRET env variable only | 200 |
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:
{"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
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Auth.js UntrustedHost in Production
A local reproduction separates the host-trust guard from proxy configuration, environment inference and later sign-in failures.
NextAuth login failed: handle CredentialsSignin, v4 and v5
Reproduced on next-auth 4.24.15 and 5.0.0-beta.32: what signIn() returns on a wrong password, why the accepted if (ok) check breaks on v5, and how to get a usable error code.
Next.js Build Passed But Production Broke
A green build only proves the bundle compiled. This postmortem walks through three production-only failures: missing Vercel env vars, Edge runtime imports, and cache behavior that changed after deploy.


