Validate configuration in one schema and fail the build
List every required variable once, reject missing or empty values, and run the check before the build so a bad configuration fails the build instead of reaching production. Include the public site URL and every secret the server reads.
import { z } from "zod";
const schema = z.object({
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().min(1),
NEXT_PUBLIC_SITE_URL: z.string().url(),
});
// run before the build: a missing or empty value throws
schema.parse(process.env);
Set each variable for the right environment, then redeploy
Confirm the variable is assigned to Production and not only Preview or Development. Redeploy after any change, and rebuild for NEXT_PUBLIC_ values, because the running deployment keeps what it was built with. In CI, map each secret into the step that builds or deploys with the secrets context.
Rotate keys with an overlap
Add the new value, redeploy, check that production traffic works, and only then disable the old key at the provider. A function that was not redeployed still holds the old secret, so check every service that reads the key.
Keep origins and hostnames in one list
Derive the CORS allowlist, the auth redirect list and the site URL setting from the same set of production origins. Echo back an origin only if it is in the list, send Vary: Origin, and answer the OPTIONS preflight with the same headers.
const ALLOWED = new Set([
"https://www.example.com",
"https://app.example.com",
]);
export function corsHeaders(origin: string | null) {
if (!origin || !ALLOWED.has(origin)) return {};
return {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Credentials": "true",
"Vary": "Origin",
};
}
Make the redirect, proxy mode and protection scope agree
If the domain loops behind Cloudflare, either remove the origin's HTTPS redirect under Flexible, or switch to Full (strict) with a certificate on the origin. Check that the origin does not redirect HTTPS back to HTTP. If monitors see a protection screen, check the Deployment Protection scope for the project.
curl -sI http://www.example.com | grep -iE '^(HTTP|location)'
Fix the cause of a failed renewal before retrying
Find why validation fails (a stale or wrong validation record, a proxy in the way, a record pointing elsewhere), correct it, then retry once. Test against the CA's staging environment where you can, because revoking certificates does not reset Let's Encrypt limits.
Run a smoke test against the real production domain after every deploy
Fetch the page, check that the status is 200, and send a preflight from the real origin. A fuller test signs in, writes a record and reads it back through the production domain.
BASE=https://www.example.com
API=https://api.example.com
test "$(curl -s -o /dev/null -w '%{http_code}' "$BASE")" = 200
curl -si -X OPTIONS "$API/items" \
-H "Origin: $BASE" \
-H "Access-Control-Request-Method: POST" | grep -i '^access-control'