# Deploy Next.js on a VPS: from build to HTTPS

Your application works locally. On a VPS, it also needs to survive an SSH disconnect, serve its assets and handle forms behind your domain. This guide follows that path from a production build to public verification.

The examples target a Next.js application with conventional build and start scripts, on Linux with systemd and an existing Nginx reverse proxy. Adapt paths, the service account and domain to your environment. A static export and a request-rendered application have different deployment needs.

## Prepare an identifiable release

Start from a known commit, its lockfile and a Node version compatible with the installed Next.js release. For npm, npm ci installs dependencies from that lockfile. Keep the existing package manager and reproducible installation mode for pnpm or Yarn.

Build in a release directory or CI rather than inside a running server’s output directory. A build can execute application code and contact services. Supply the variables it needs without putting secrets in public. NEXT_PUBLIC_ values are intended for the browser and are embedded during the build.

```sh
node --version
npm ci
npm run build
```

## Start the production server on loopback

Check that the start script runs next start. The development server is not the process to keep in production. Binding to 127.0.0.1 limits direct access to the server and its local proxy, keeping the application port off the public interface.

In another terminal, inspect a real page with curl -i http://127.0.0.1:3000/. Check its content and assets. If you selected output: standalone, use the generated server.js and its expected directory structure, including public and .next/static. That mode has its own entrypoint.

```sh
npm run start -- --hostname 127.0.0.1 --port 3000
```

## Let systemd manage the process

A systemd unit runs the service independently of your session. This example assumes an existing webapp account and a release at /srv/my-next-app. Check the Node path with command -v node and adapt ExecStart. The account needs to read the release and write only to the application locations that require it.

Keep server variables in the environment file with permissions appropriate for the service account. After installing the unit in your environment, validate it with systemd-analyze verify, reload systemd and start the service. Inspect its journal if startup fails. Automatic restarts do not replace investigating a memory-related exit.

```ini
[Unit]
Description=My Next.js application
After=network.target

[Service]
Type=simple
User=webapp
WorkingDirectory=/srv/my-next-app
Environment=NODE_ENV=production
EnvironmentFile=/etc/my-next-app.env
ExecStart=/usr/bin/node /srv/my-next-app/node_modules/next/dist/bin/next start --hostname 127.0.0.1 --port 3000
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

## Route the domain to the application

Inside the domain’s server block, a location can forward requests to the local application. The following fragment covers HTTP and streaming; integrate it with the existing configuration and run nginx -t before reloading. It is not a complete TLS configuration.

The domain needs to resolve to the VPS and have a valid certificate with working renewal. Preserve the host and original protocol for the application. Start without a shared dynamic-response cache: private pages, cookies and navigation variants need an explicit policy. If the application uses WebSockets, add the appropriate Nginx Upgrade header handling separately.

```nginx
location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_buffering off;
}
```

## Verify the public site

A 200 response from the homepage does not verify the whole application. Exercise a dynamic route, a missing page, login, logout and a form. Check redirects, secure cookies, images and navigation in a browser.

Test a streaming page as well. If content arrives all at once through the domain but progressively on loopback, examine buffering at every intermediary. Keep the previous release and its configuration before switching traffic. Reverting application code also requires compatibility with any database migrations.

## Size and evolve the deployment

Measure idle memory, traffic peaks and deployment overlap. Two releases can coexist briefly, and building on the VPS adds its own peak. The database and proxy belong in the total budget too.

To evaluate PRNext, retain the Next.js reference and build a candidate in another directory. Verify your application’s behavior and performance before changing its runtime.

[Size VPS memory for Next.js](/blog/nextjs-ram-vps-sizing)

[Check PRNext compatibility before migrating](/blog/prnext-nextjs-compatibility-migration)

## Sources

[Next.js: self-hosting](https://nextjs.org/docs/app/guides/self-hosting)

[Next.js: environment variables](https://nextjs.org/docs/app/guides/environment-variables)

[Next.js: standalone output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output)

[systemd: service units](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html)

[Nginx: proxy module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)

Published: 2026-10-30

Modified: 2026-10-30

Author: PRNext

---

Language: en

Canonical: https://prnext.dev/blog/deploy-nextjs-vps

Index: https://prnext.dev/llms.txt
