Updating and Troubleshooting
Updating an existing deployment
Section titled “Updating an existing deployment”Rebuild on your build machine and replace the deployed files. Stop the service before copying so users aren’t served a half-updated bundle.
Docker
docker build \ --build-arg NEXT_PUBLIC_API_BASE_URL="https://api.example.com" \ -t my-app:latest ./webdocker stop my-app && docker rm my-appdocker run -d --name my-app -p 127.0.0.1:3000:3000 --env-file ./web/.env.local --restart unless-stopped my-app:latestLinux
sudo systemctl stop my-appsudo rsync -a --delete --exclude='.env' /tmp/new-build/ /opt/my-app/ # --exclude keeps your .env; adjust source pathsudo systemctl start my-appWindows
An update replaces only the app files in the Node deployment folder (C:\apps\my-app\). Two things stay put:
- The server’s
.env. It lives in the deployment folder, not the build output, so the file copy in step 4 leaves it alone; don’t delete it. If you’ve added a server-side variable since the last deploy, update the server’s.envnow. A newNEXT_PUBLIC_*variable instead needs a rebuild. See Changing an environment variable. - The IIS site and its
web.config. They live in the separate site folder (C:\apps\my-app-site\), not the app folder, so you don’t re-run any of the IIS reverse-proxy steps when shipping a new build.
# 1. On your build machine, rebuild exactly as in the first deploy: production values# in web\.env.local, then run (cd web ; npm ci ; npm run build).
# 2. Stop the service so users aren't served a half-updated bundle:nssm stop MyApp
# 3. Delete the old build output so stale, hashed JS/CSS chunks don't linger.# This removes only build output — it leaves .env and the logs folder in place:Remove-Item -Recurse -Force "C:\apps\my-app\.next", "C:\apps\my-app\node_modules"
# 4. Copy the new build in, as in the first deploy:# - contents of web\.next\standalone\ -> C:\apps\my-app\ (server.js at the root)# - web\.next\static\ -> C:\apps\my-app\.next\static\# - web\public\ -> C:\apps\my-app\public\
# 5. Start it again:nssm start MyAppA few seconds of downtime is normal during an update. For zero-downtime deployments, run two instances behind the reverse proxy and update them one at a time.
Changing an environment variable
Section titled “Changing an environment variable”Which route you take depends on the kind of variable:
- A
NEXT_PUBLIC_*variable (browser-facing): it’s baked into the app at build time, so editing a file changes nothing. Updateweb/.env.local, rebuild, and redeploy, following the full steps above. - A server-side variable (anything else, such as backend URLs and secrets): edit the server’s env file (the
.envin the deploy folder on Windows/Linux, orweb/.env.localon the host for Docker) and restart the service (nssm restart MyAppon Windows,sudo systemctl restart my-appon Linux, or recreate the container on Docker). No rebuild needed; the server re-reads the file on restart. One exception: if the value is also used during the build (for example a backend URL your app reads while pre-rendering pages, or a value referenced innext.config.ts), a restart won’t pick it up, so updateweb/.env.localand rebuild instead. If you’re unsure which kind you have, rebuilding always works.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| Login works, then immediately bounces back to the login page | The app’s session check isn’t receiving the cookie. Most likely, in order: (1) the cookie’s Domain is host-only, so it never reaches the app’s host (it must be set to the shared parent, e.g. Domain=example.com); (2) the frontend and auth endpoint aren’t on the same registrable domain; (3) for external-provider sign-in, the SameSite=Strict first-load withholding isn’t being handled (the generated proxy.ts meta-refresh was stripped). See Cookie domains. |
| 500 error on first page load | A NEXT_PUBLIC_* variable wasn’t set when you built. These bake into the app during next build, so setting them at run time has no effect. Put the value in web/.env.local and rebuild. See build-time vs run-time variables. |
| Mixed-content warnings in the browser console | A NEXT_PUBLIC_* variable points at http:// but the site is running over https://. All public URLs in production must be HTTPS. Fix the value in web/.env.local and rebuild. See build-time vs run-time variables. |
| Server-side values missing at run time (a backend URL is undefined, a required secret is empty) | The server didn’t find a .env in its folder. Confirm you copied web/.env.local into the deployment folder as .env, next to server.js (Windows/Linux); for Docker, check the --env-file path. See build-time vs run-time variables. |
| Service won’t start after a server reboot | Linux: sudo systemctl enable my-app wasn’t run. Windows: the NSSM service start type isn’t Automatic. |
| Windows: site loads but every request returns HTTP 502.3 Bad Gateway (or a blank IIS error) | ARR reached IIS but couldn’t get a response from the app it forwards to. Two causes: (1) ARR’s proxy engine is off: enable it once at Application Request Routing Cache → Server Proxy Settings → Enable proxy (this is the cause only if the proxy never worked). (2) The app is down or on a different port: confirm it’s listening with curl.exe http://127.0.0.1:3000/ on the server, and that the rewrite uses the right port and http (not https). See Set up IIS as the reverse proxy. |
| Windows: HTTP 500.19: “configuration section … is locked at a parent level” | Your site web.config contains a <proxy> element, which is a server-level setting and can’t live in a site file. Remove it and enable the proxy at the server level instead. See Set up IIS as the reverse proxy. |
| Windows: HTTP 500: “The specified server variable … is not allowed here” | The rewrite rule sets X-Forwarded-* headers that aren’t on URL Rewrite’s allowed list. Add HTTP_X_FORWARDED_PROTO and HTTP_X_FORWARDED_FOR under your site’s URL Rewrite → View Server Variables. See Set up IIS as the reverse proxy. |
| Windows: HTTP 400: “The request hostname is invalid” | Two possible sources. IIS front door: the site binding doesn’t match the hostname, or (with two HTTPS sites on 443) Require SNI isn’t ticked. Your backend: its host allow-list doesn’t include the hostname the proxy forwards. This is separate from CORS. See Deploying a Separate Auth API. |
| Windows: sign-in redirects land on the wrong host (clicking sign in jumps to your auth host instead of the identity provider, or lands back on the auth host instead of your app after login) | ARR’s Reverse rewrite host in response headers is rewriting the host of every redirect the auth backend returns. Untick it in Server Proxy Settings (it’s a server-level setting). See Deploying a Separate Auth API. |
node: command not found when the service starts | Node 22 isn’t installed, or the service environment has a different PATH than your interactive shell. Use the full path to node in your service file. See the which node note in the Linux guide. |
| API calls return CORS errors | The frontend and backend API are on different domains. Either put them under one domain via the reverse proxy, or configure your backend to send the appropriate CORS headers. |
| Certificate expired and the site is unreachable | Auto-renewal stopped silently. Linux: run sudo certbot renew --dry-run to test. Windows: check the win-acme scheduled task in Task Scheduler. |