🚀 Executive Summary

TL;DR: Deploying Next.js applications on a VPS requires running them as background services to prevent termination when the SSH session closes. Solutions range from using process managers like PM2 for robust Node.js application management to containerizing with Docker and orchestrating via systemd for isolated, OS-managed environments.

🎯 Key Takeaways

  • Running `npm run start` directly creates a foreground process that terminates with the SSH session (SIGHUP signal).
  • PM2 is a professional Node.js process manager that keeps applications alive, restarts them on crashes, manages logs, and can be configured to start on server reboot using `pm2 startup` and `pm2 save`.
  • Docker allows packaging Next.js applications into isolated, reproducible containers, which can then be managed by `systemd` for automatic restarts and OS-level lifecycle control.
  • Always use a reverse proxy (e.g., Nginx, Caddy) in front of your Next.js application to handle SSL termination, caching, and security, rather than exposing the Node.js port directly.

Deploying Next.js on a VPS is easier than you think

SEO Summary: Learn to properly deploy and daemonize your Next.js application on a VPS. Move beyond simple `npm run start` and use professional tools like PM2 or Docker with systemd to ensure your app stays online, even after you log out.

So You SSH’d Into a VPS and Ran `npm run start`. Now What?

I remember it like it was yesterday. We had a junior dev, brilliant kid, who was tasked with deploying a small marketing site on a fresh DigitalOcean droplet. An hour later, he pings me on Slack: “Darian, the site is live!” We all checked. It worked. High-fives all around. Then, about 20 minutes later, I get a frantic message: “The site is down! I didn’t touch anything!” I asked him one simple question: “Did you close your terminal window?” The silence was deafening. We’ve all been there. That feeling in your gut when you realize a production service was tied directly to your local SSH session is a rite of passage, but one we can definitely help others avoid.

The “Why”: Your Shell Isn’t a Babysitter

When you run npm run start or next start directly in your terminal, you’re starting a foreground process. It’s a child of your shell session. Your shell is a doting parent, but when it’s time to go (i.e., you close the terminal or get disconnected), it sends a “hang up” signal (SIGHUP) to all its children, telling them to terminate. Poof. Your Next.js server is gone.

The core problem is that you aren’t running your application as a true background service or “daemon”—a process that runs independently of any user session and is managed by the operating system itself. Let’s fix that, starting with the hackiest way and working our way up to the “right” way.

The Fixes: From Duct Tape to DevOps Nirvana

Solution 1: The “It’s 3 AM and I Just Need It to Work” Fix (nohup)

This is the classic, old-school Unix way to disown a process. nohup stands for “no hang up,” and it does exactly what it says on the tin: it tells the process to ignore that SIGHUP signal we talked about. The ampersand (&) tells the shell to run the command in the background immediately.

First, make sure you’ve built your app:

npm run build

Then, run the start command with nohup:

nohup npm run start &

This will dump the output into a file called nohup.out in the current directory. You can now safely close your terminal. Your app will keep running. But… it’s a terrible long-term solution. If the app crashes, it stays crashed. There’s no automatic restart, no easy log management, and it’s just… messy.

Warning: I’m showing you this so you know it exists, not so you use it on prod-web-01. It’s fine for a quick-and-dirty staging preview, but it’s not a real deployment strategy. It’s the equivalent of using a wrench as a hammer. It works, but you’ll hurt yourself eventually.

Solution 2: The “I’m a Professional” Fix (PM2)

Now we’re talking. PM2 is a process manager for Node.js applications. It’s a purpose-built tool that handles everything we need: keeping the app alive, restarting it on crashes, managing logs, and even running it in a cluster to take advantage of all CPU cores. This is the industry standard for a reason.

First, install PM2 globally:

npm install pm2 -g

After building your Next.js app (npm run build), you can start it with PM2. The key is to tell PM2 to run the npm command itself.

pm2 start npm --name "my-awesome-app" -- run start

Let’s break that down:

  • pm2 start npm: Tells PM2 to start the program named `npm`.
  • --name "my-awesome-app": Gives the process a friendly name you can reference later.
  • -- run start: These are the arguments passed to the `npm` command. The -- is important; it separates PM2’s arguments from your script’s arguments.

Your app is now running and managed by PM2. But there’s one more crucial step: making sure PM2 starts when the server reboots.

pm2 startup
pm2 save

PM2 will generate a command for you to run (it will have sudo). Run that command, then run pm2 save. This saves your current process list and ensures it will be restored on server restarts. You are now running a robust, production-ready service.

Solution 3: The “Cloud Architect” Way (Docker & systemd)

Sometimes, you need more than just process management. You need a completely isolated, reproducible environment. You need to package your app, its dependencies, its environment variables, and its Node.js version into a single, portable unit. You need Docker.

This approach has two parts: creating a Docker image for your app, and then using the server’s own init system (systemd on most modern Linux distros) to manage the container. It’s the belt-and-suspenders approach.

Part A: The Dockerfile

Create a file named Dockerfile in your project root. This is a multi-stage build, which keeps your final image small and secure.

# Stage 1: Build the application
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Production image
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["npm", "start"]

Build your image on the server with: docker build -t my-nextjs-app .

Part B: The systemd Service

Now, we tell the operating system how to manage our Docker container. Create a file at /etc/systemd/system/nextjs-app.service:

[Unit]
Description=My Awesome Next.js App Container
After=docker.service
Requires=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker run --rm -p 3000:3000 --name my-nextjs-app-container my-nextjs-app
ExecStop=/usr/bin/docker stop my-nextjs-app-container

[Install]
WantedBy=multi-user.target

Now, enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable nextjs-app.service
sudo systemctl start nextjs-app.service

Your Next.js app is now running in an isolated container, managed by the OS itself. It will restart on crashes and on server reboots. This is the pinnacle of single-VPS deployment reliability.

Pro Tip: Don’t forget your reverse proxy! No matter which of these solutions you choose, you should never expose your Node.js port (3000) directly to the internet. Always put a web server like Nginx or Caddy in front of it to handle SSL termination, caching, and security.

So, next time you’re on a fresh VPS, step away from the raw npm run start. Your future self (and your lead engineer) will thank you.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How do I ensure my Next.js application stays running on a VPS after I close my SSH terminal?

To ensure your Next.js application runs persistently, use a process manager like PM2 (`pm2 start npm –name “my-app” — run start` followed by `pm2 startup` and `pm2 save`) or containerize it with Docker and manage the container’s lifecycle using `systemd`.

âť“ How do the different deployment methods (nohup, PM2, Docker with systemd) compare for Next.js on a VPS?

`nohup` is a basic, unreliable method for quick background execution without restart capabilities. PM2 is a robust Node.js process manager offering automatic restarts and log management. Docker with `systemd` provides a highly isolated, reproducible environment managed by the operating system, ideal for complex deployments.

âť“ What is a common pitfall when deploying Next.js on a VPS and how can it be avoided?

A common pitfall is directly exposing the Node.js application’s port (e.g., 3000) to the internet. This should be avoided by placing a reverse proxy like Nginx or Caddy in front of the application to handle SSL termination, caching, and enhance security.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading