Updating and Troubleshooting
Updating an existing deployment
Section titled “Updating an existing deployment”Rebuild on your build machine, then replace the deployed files. Stop the service before you copy, so users aren’t served a half-updated app.
Docker
Run from the top of your project:
docker build \ --build-arg NEXT_PUBLIC_API_BASE_URL="https://api.example.com" \ -t my-app:latest .docker 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 the source path.sudo systemctl start my-appWindows
An update replaces only the app files in the deployment folder (C:\apps\my-app\). Two things stay where they are:
- The server’s
.env: it lives in the deployment folder, not the build output, so the 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 needs a rebuild instead. See Changing an environment variable. - The IIS site and its
web.config: they live in the separate site folder (C:\apps\my-app-site\), so you don’t repeat any of the IIS reverse proxy steps for 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 app:nssm stop MyApp
# 3. Delete the old build output so stale JS and CSS files 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. To avoid it, run two copies of the app behind the reverse proxy and update them one at a time.
Changing an environment variable
Section titled “Changing an environment variable”What you do depends on the kind of variable.
| Kind | What to do |
|---|---|
A NEXT_PUBLIC_* variable | It’s built into the app, so editing a file on the server changes nothing. Update web/.env.local, rebuild, and redeploy using the steps above. |
| A server-side variable, such as a backend URL or a secret | Edit the server’s env file and restart the service. No rebuild is needed, because the server re-reads the file when it starts. |
Where the server’s env file is, and how to restart:
| Platform | Env file | Restart |
|---|---|---|
| Windows | .env in the deployment folder | nssm restart MyApp |
| Linux | .env in the deployment folder | sudo systemctl restart my-app |
| Docker | web/.env.local on the host | Remove and re-run the container |
One exception: if a server-side value is also used during the build, a restart won’t pick it up. Examples are a backend URL the app reads while pre-rendering pages, or a value used in next.config.ts. Update web/.env.local and rebuild instead. If you’re not sure which kind you have, a rebuild always works.
Troubleshooting
Section titled “Troubleshooting”If the site won’t load at all
Section titled “If the site won’t load at all”If the browser shows a connection error rather than a page from your server, the error name tells you where to look. Work down the table in order:
| Browser error | What to check |
|---|---|
DNS_PROBE_FINISHED_NXDOMAIN, or “This site can’t be reached” with a name lookup failure | DNS: there’s no record for the hostname. This is common right after you add a new subdomain. Confirm with nslookup <hostname>. |
ERR_CONNECTION_TIMED_OUT | The firewall: inbound 80 or 443 isn’t open on the server, or on a network or cloud firewall in front of it. |
ERR_CONNECTION_REFUSED | Nothing is listening on that port. The site is stopped, or you’re using the wrong port. |
ERR_CONNECTION_RESET | An HTTPS and HTTP mismatch: usually an https:// address pointed at a plain HTTP port, such as a backend port with no proxy. Put it behind a proxy that handles HTTPS, or fix the address. |
A page from the server, such as 400, 500 or 502 | The request reached a server. See the table below. |
Common problems
Section titled “Common problems”| Symptom | Likely cause |
|---|---|
| Sign-in works, then immediately returns to the sign-in page | The app’s session check isn’t receiving the cookie. In order of likelihood: (1) the cookie’s Domain is host-only, so it never reaches the app’s host. It must be the shared parent, such as Domain=example.com. (2) The frontend and auth endpoint aren’t on the same registrable domain. (3) For sign-in through an identity provider, the first page load after the redirect withholds the SameSite=Strict cookie and nothing refreshes it. See Cookie domains and Deploying a Separate Auth API. |
| A 500 error on the first page load | A NEXT_PUBLIC_* variable wasn’t set when you built. These are built into the app, so setting them at run time has no effect. Put the value in web/.env.local and rebuild. See Environment variables. |
A NEXT_PUBLIC_* value still shows the old address, often localhost, after you rebuilt | You rebuilt before updating web/.env.local, so it still has the template’s localhost defaults. Set your production values in web/.env.local, then rebuild. |
| The deployed app shows mock data | NEXT_PUBLIC_USE_MOCK_API was set when you built. Remove it from web/.env.local and rebuild. |
| Mixed-content warnings in the browser console | A NEXT_PUBLIC_* variable points at an http:// address but the site runs on https://. Every public address in production must use HTTPS. Fix the value in web/.env.local and rebuild. |
| Server-side values are missing at run time, such as an undefined backend URL or an empty secret | The server didn’t find a .env in its folder. Check you copied web/.env.local into the deployment folder as .env, next to server.js. For Docker, check the --env-file path. |
| The service doesn’t start after a server reboot | Linux: sudo systemctl enable my-app wasn’t run. Windows: the NSSM service’s start type isn’t set to Automatic. |
node: command not found when the service starts | Node.js 22 isn’t installed, or the service has a different PATH from your terminal. 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. Put them under one domain through the reverse proxy, or configure your backend to send the right CORS headers. |
| The certificate expired and the site is unreachable | Automatic renewal stopped without warning. Linux: run sudo certbot renew --dry-run to test it. Windows: check the win-acme scheduled task in Task Scheduler. |
Windows and IIS problems
Section titled “Windows and IIS problems”| Symptom | Likely cause |
|---|---|
| Every request returns HTTP 502.3 Bad Gateway, or a blank IIS error | IIS couldn’t get a response from the app it forwards to. (1) ARR’s proxy is off: turn it on once at Application Request Routing Cache, then Server Proxy Settings, then Enable proxy. This is only the cause if the proxy has never worked. (2) The app is down or on a different port: on the server, check it’s listening with curl.exe http://127.0.0.1:3000/, and check the rewrite uses the right port and http, not https. See Set up IIS as the reverse proxy. |
| HTTP 500.19: “configuration section … is locked at a parent level” | Your site’s web.config contains a <proxy> element. That’s a server-level setting and can’t live in a site’s file. Remove it and turn the proxy on at the server level instead. |
| 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, then View Server Variables. |
| HTTP 400: “The request hostname is invalid” | It comes from one of two places. The IIS site: the binding doesn’t match the hostname, or, with two HTTPS sites on 443, Require Server Name Indication isn’t ticked. Your backend: its host allow-list doesn’t include the hostname the proxy forwards. To tell them apart, call the backend directly with curl.exe -H "Host: <name>" http://127.0.0.1:<port>/<path>, then compare with a call through IIS. See Deploying a Separate Auth API. |
| Sign-in redirects land on the wrong host, such as your auth host instead of the identity provider | ARR’s Reverse rewrite host in response headers option is rewriting the host of every redirect the auth backend returns. Untick it in Server Proxy Settings. See Deploying a Separate Auth API. |