TutorialIntermediate · 30 min

Set Up a Simple Client Portal on a Budget VPS

A practical way for small firms to launch a lightweight client portal on their own VPS without paying for a heavy SaaS stack.

Published Apr 18, 2026 · Updated Apr 18, 2026

Summary

You'll end up with a lightweight client portal running on an Ubuntu VPS, managed by PM2, fronted by Caddy with automatic HTTPS, and structured for future upgrades.

Why this setup fits a small firm

A simple client portal does not need to begin with an expensive SaaS subscription or a bloated platform. If your goal is to give clients a secure place to log in, check status, exchange files, and handle a small set of recurring tasks, a budget VPS can be a practical first step.

This tutorial continues the planning approach we covered earlier: start small, keep the scope clear, and avoid building more than your workflow actually needs. Instead of treating the portal like a full product suite, you will deploy a lightweight foundation that is easier to control, cheaper to run, and simpler to expand later.

What this tutorial covers

In this walkthrough, you will prepare an Ubuntu VPS, install the runtime your portal needs, deploy the application, place Caddy in front of it for automatic HTTPS, and add a few production basics such as environment variables, firewall rules, and process management.

This is a good fit when you already know the minimum scope of your portal and want a straightforward path to getting it online.

Before you begin

This guide assumes:

  • you have an Ubuntu 24.04+ VPS with sudo access
  • you control the DNS for your domain or subdomain
  • your portal app is already built in Next.js or another Node-based stack
  • you are comfortable using SSH in a terminal

If you are still deciding what your portal should actually handle, start with the planning side first before deployment.

What You Need

Required

  • Ubuntu 24.04+ VPS with sudo access
  • A domain or subdomain pointed to the VPS
  • SSH access to the server

Recommended

  • A dedicated deploy user or at least a clear deployment folder structure
  • A production-ready app repository with environment variables documented
  • Basic firewall access for SSH, HTTP, and HTTPS

Steps

Step 1

Define the minimum portal scope before you deploy

Make sure you are deploying a focused phase 1 portal, not an undefined future system.

Before touching the server, write down the smallest useful version of your portal.

For example, your phase 1 might only include:

  • secure login
  • status display
  • file upload or file delivery
  • a small request list
  • one internal workflow behind the scenes

What you want to avoid is deploying infrastructure for a portal that still has no clear boundary.

A simple production deployment becomes much easier when the app only needs to support a few real workflows first.

Expected result

You have a short written scope for phase 1 and can explain what the portal will and will not handle.

Step 2

Prepare the VPS and update base packages

Start with a clean server state before installing your application stack.

bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git ufw

If you have not already configured the firewall, allow the basic ports you will need:

bash
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

This gives you a simple baseline for a public web service without leaving the host wide open.

Expected result

The server packages are updated, git and curl are installed, and the firewall allows SSH, HTTP, and HTTPS.

Step 3

Install Node.js and PM2

Set up the runtime and process manager your portal app will use in production.

bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g pm2

Check the installation:

bash
node -v
npm -v
pm2 --version

PM2 will help you keep the app running and restart it after reboots or crashes.

Expected result

node -v shows Node.js 22.x, npm -v prints a version, and pm2 --version returns successfully.

Step 4

Clone the portal application and install dependencies

Move the application onto the server and prepare it for a production build.

bash
cd /var/www
sudo mkdir -p client-portal
sudo chown $USER:$USER client-portal
cd client-portal

git clone <your-repo-url> app
cd app
npm install

If your repository already includes a lockfile, keep it in place so production installs stay consistent.

Expected result

The application source code is on the server, and npm install finishes without dependency errors.

Step 5

Add environment variables and build the app

Configure the production settings your portal needs before starting it.

Create your production environment file based on the variables your app expects.

bash
cp .env.example .env.production
nano .env.production

Then build the application:

bash
npm run build

Typical production variables might include:

  • app URL
  • database connection string
  • authentication secret
  • file storage configuration
  • email delivery settings

Do not hard-code these values into the application itself.

Expected result

Your production environment file is in place, and npm run build completes successfully.

Step 6

Start the portal with PM2

Run the app in a way that survives disconnects and can restart automatically.

For a typical Next.js deployment, you can start the production server like this:

bash
pm2 start npm --name client-portal -- start

Save the process list and enable startup on reboot:

bash
pm2 save
pm2 startup

PM2 will print one more command after pm2 startup. Run that command exactly as shown.

Then confirm the process is online:

bash
pm2 status

Expected result

PM2 shows the client-portal process as online, and the startup configuration has been saved.

Step 7

Configure Caddy as a reverse proxy with HTTPS

Place Caddy in front of the app so your portal is served securely over your domain.

Install Caddy:

bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
sudo apt install -y caddy

Edit the Caddyfile:

bash
sudo nano /etc/caddy/Caddyfile

Replace the contents with your domain and local app port:

caddy
portal.example.com {
    reverse_proxy 127.0.0.1:3000
}

Then reload Caddy:

bash
sudo systemctl reload caddy
sudo systemctl status caddy

Once DNS is pointed correctly, Caddy can request and renew HTTPS certificates automatically.

Expected result

Caddy is running, your domain resolves to the VPS, and the portal loads over HTTPS.

Step 8

Run a final production check

Confirm the deployment works as a real client portal, not just as a running web app.

Before calling the deployment finished, test the parts that matter to actual users.

Check at least the following:

  • the portal loads on the correct domain
  • HTTPS is active
  • login works
  • the main dashboard or landing screen renders correctly
  • any file-related workflow behaves as expected
  • errors are not visible in the browser console or PM2 logs

You can inspect app logs with:

bash
pm2 logs client-portal

This final check matters because a portal is only useful when the business workflow works, not just when the server is online.

Expected result

You can access the portal on its live domain, core user flows work, and no blocking runtime errors appear in the logs.

What happens next

Next steps

Once the portal is live, the next improvements usually involve the parts that make it useful in daily operations: secure logins, file upload handling, backups, role-based access, and integrations with the systems your team already uses.

You do not need all of that on day one. The better approach is to get a stable version online first, confirm that the scope is working, and then add automation in layers.

If you want help turning a simple deployment into a production-ready portal with workflow design, secure hosting, and long-term ownership, see our Services.

Need help implementing this?

We build custom solutions for small businesses. Let us help with your next project.