HR's Journal
Technical guide

Deploying Next.js and Prisma Safely on a Shared VPS

A production playbook for inventory, isolated runtimes, PostgreSQL, systemd, TLS, backups, and clean operational handoff.

ByHabibur Rahman

Editor & Engineer

5 min read

Production deployment is not the moment to discover what else a server is doing. A VPS may already host databases, proxy rules, containers, certificates, or unrelated applications. The safest deployment begins by treating that existing state as a contract, not as empty space.

This guide describes a repeatable way to place a Next.js application backed by Prisma and PostgreSQL on a shared Linux VPS. The goal is not merely to make the homepage load. It is to leave behind a service that can be inspected, upgraded, backed up, rolled back, and handed to another operator without guesswork.

Start with a read-only inventory

Before installing anything, record the current listeners, services, storage use, proxy configuration, firewall policy, and certificate owner. Commands such as ss -lntup, systemctl --type=service --state=running, docker ps, df -h, and a focused review of the active web-server configuration answer the important questions quickly.

The inventory establishes boundaries. If another proxy already owns ports 80 and 443, the application should listen only on a loopback port. If PostgreSQL already exists, adding a dedicated database and role is usually safer than installing a competing database stack. If automated certificate renewal already exists, the new hostname should fit that mechanism rather than bypass it.

Write down these facts before changing them. That small habit makes later diagnosis dramatically easier because you know which state preceded the deployment.

Separate the application from the machine

A maintainable layout uses stable operational paths and immutable releases:

/opt/hrsjournal/
  current -> releases/20260822T105546Z
  releases/
  shared/
  backups/
  OPERATIONS.md

Each deployment receives a timestamped release directory. Configuration and durable data live outside that directory. A current symbolic link identifies the active version. This layout makes the running source unambiguous and turns rollback into a link change plus a service restart.

The Node.js runtime should also be explicit. Relying on whichever node happens to appear first in a shell path creates a common mismatch: an interactive build works, while systemd starts an older binary. Pin the service to an absolute runtime path and verify node --version from the same execution context used by the service.

Give the database a narrow identity

Create a dedicated PostgreSQL role and database for the application. The role should own its database but should not be a superuser. Keep the connection string in a root-readable environment file, not in the repository or the unit file.

Prisma migrations belong in the release procedure. Run prisma migrate deploy against the production connection before switching traffic, and fail the release if migration fails. Do not use development migration commands on production. A schema change is part of the application version, so the migration result should be logged alongside the release identifier.

Before a deployment that changes the schema, create a logical database backup with pg_dump. Then verify that the output exists and is non-empty. A backup command returning successfully is useful; a dated artifact with known size and retention is operationally meaningful.

Run Next.js as a supervised loopback service

Build the application inside the release directory and run it under systemd. The process should bind to 127.0.0.1 on a dedicated port, leaving public TLS termination to the existing reverse proxy.

The unit should declare its working directory, environment file, absolute executable, restart policy, and a reasonable stop timeout. Security hardening such as NoNewPrivileges=true, private temporary storage, and a restrictive file-creation mask is valuable as long as it does not block required runtime paths.

After enabling the unit, inspect both systemctl status and recent journal entries. A process being marked active does not prove that its database connection, environment validation, or request path works. Probe the loopback health endpoint directly and require a successful database check.

Put the reverse proxy in charge of the edge

The public web server should redirect HTTP to HTTPS and proxy HTTPS requests to the loopback application. Preserve the original host and forwarding headers so canonical URLs, secure cookies, request logging, and rate limiting see the real request context.

TLS is incomplete until renewal is automated. Use the certificate method compatible with the active web server, install a renewal timer, and test renewal without waiting for expiry. If certificate files must be copied into a server-specific location, automate that synchronization and reload step as a deploy hook.

Test the edge from outside the machine. Check the HTTP redirect, certificate hostname, TLS response, security headers, compressed asset delivery, and the application health endpoint. A localhost success can coexist with broken DNS, proxy routing, or certificate selection.

Make the handoff part of the deployment

An operations guide is not optional polish. It should name the service, active release path, environment location, loopback port, database, log command, backup command, restore outline, deploy sequence, and rollback sequence. It should never contain passwords.

Schedule backups and define retention. Confirm the timer is enabled, then run it once and inspect the artifact. Keep at least one copy outside the active release directory. Application releases are replaceable; user accounts and published content are not.

Finally, verify the real user paths: homepage, article page, login, admin access, robots file, sitemap, and health probe. Recheck the previously inventoried co-hosted services. A deployment is complete only when the new application works and the server's earlier responsibilities still work.

The result is deliberately boring: one supervised service, one explicit runtime, one narrow database identity, one proxy route, dated backups, and a reversible release pointer. Boring operations are a feature. They make future changes faster because every component has a clear owner and every failure has a smaller search area.

DeploymentNext.jsPostgreSQLPrisma