Nginx Reverse Proxy: Host Multiple Apps on One VPS

One VPS can run your portfolio, API, and dashboards at the same time — but only one program can own port 80. This guide shows how Nginx as a reverse proxy routes each subdomain to the right app on its own internal port. Covers server blocks, proxy headers, free HTTPS with Certbot, WebSocket support, subdomains vs paths, and the misconfigurations that waste most students' evenings.

Written by Projectech7 min readPublished
For Web development students who have one app on a VPS and want to host several projects — API, frontend, dashboards — on the same cheap server, with HTTPS. Topics: Nginx, Ubuntu Linux, Certbot, DNS
Blueprint-style technical illustration of a server rack sending web traffic through a central gateway node that distributes it to multiple application servers.
Illustration generated for this guide.
In this guide

One cheap VPS can comfortably run your portfolio site, a Node API for your project, and a Python dashboard for your sensors — at the same time. The catch is that only one program can listen on port 80, the port browsers use for HTTP. A reverse proxy solves exactly this: Nginx takes ownership of ports 80 and 443, reads the domain name in each incoming request, and forwards it to the right app running quietly on its own internal port. This guide walks you through the full setup — one subdomain per app, a clean Nginx config, free HTTPS with Certbot, WebSocket support, and the mistakes that waste most students' evenings.

The problem: one port 80, many apps

Browsers connect to port 80 for HTTP and 443 for HTTPS, and only one process can bind to each port at a time. So the second app you deploy has nowhere to go. The usual student workaround is to expose ports in the URL — http://your-vps-ip:3000 for the API, :5000 for the dashboard. That works, until it doesn't:

  • Port numbers in URLs look unfinished and are painful to share in a report or demo.
  • College and hostel firewalls often block non-standard ports, so your project "works on my laptop" but not in the lab.
  • Getting HTTPS on https://ip:3000 is awkward; browsers warn, and some APIs refuse to talk to insecure origins.
  • Every new app means another port to remember, another firewall rule, another thing to secure.

Buying a bigger server per app defeats the whole point of a cheap VPS. The professional answer is a reverse proxy sitting in front of everything.

How a reverse proxy works

Nginx binds to ports 80 and 443. Your apps bind to private ports on the same machine — 127.0.0.1:3000, 127.0.0.1:5000, and so on. When a request arrives, Nginx reads the Host header — the domain name the browser asked for — and forwards the request to the matching app with proxy_pass. The app responds to Nginx, Nginx responds to the browser. The browser never talks to your apps directly.

One front door gives you several things for free: a single place to terminate TLS (HTTPS for every app from one setup), centralised access logs, and the ability to serve static files and cache them without touching your app code. Nginx itself is event-driven and tiny — on student traffic its overhead is effectively zero.

Step-by-step: three apps, one VPS

Assume you have three things running: a static portfolio site, a Node API on port 3000, and a Flask dashboard on port 5000. You own yourdomain.com and you want www.yourdomain.com, api.yourdomain.com, and dashboard.yourdomain.com.

1. Bind your apps to localhost. Each app should listen on 127.0.0.1 and its own port — not 0.0.0.0. Apps bound to all interfaces are reachable directly from the internet, bypassing Nginx and your HTTPS setup entirely. This is the single most common misconfiguration.

2. Point DNS at the VPS. Create A records for www, api, and dashboard, each pointing at your VPS IP. DNS changes can take a few minutes to a few hours to propagate — wait until ping api.yourdomain.com resolves to your server before continuing.

3. Install Nginx. On Ubuntu or Debian:

sudo apt update
sudo apt install nginx

4. Write one server block per subdomain. Server blocks live in /etc/nginx/sites-available/, symlinked into sites-enabled. Here is a complete, minimal block for the API:

server {
    listen 80;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The proxy_set_header lines matter more than they look. Without them, your app sees every visitor as 127.0.0.1, and features that build URLs from the request (login redirects, password-reset links) generate broken http://127.0.0.1:3000/... links. Duplicate this block for the dashboard on port 5000 with server_name dashboard.yourdomain.com.

For the static portfolio, let Nginx serve the files directly instead of proxying:

server {
    listen 80;
    server_name www.yourdomain.com;
    root /var/www/portfolio;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

5. Test and reload. Never reload blind — one typo in any server block takes every site down:

sudo nginx -t
sudo systemctl reload nginx

6. Add HTTPS with Certbot. Certbot's Nginx plugin reads your server blocks and handles certificates automatically:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -d api.yourdomain.com -d dashboard.yourdomain.com

It obtains free Let's Encrypt certificates, rewrites your server blocks to listen on 443, and sets up renewal. Certbot needs to reach your server on port 80 to prove ownership, so open port 80 in your firewall first.

7. Support WebSockets. Chat apps and live dashboards need the protocol upgrade to pass through, or you will get mysterious 400 errors:

location /socket.io/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Subdomains or paths?

You have two routing styles. Subdomains (api.yourdomain.com) give each app full isolation: separate TLS certificates, separate cookies, and no code changes — the app thinks it owns its domain. The cost is one DNS record per app. Path prefixes (yourdomain.com/api) need no extra DNS, but every app must be rewritten to serve under its prefix, and cookie and CORS behaviour gets tangled. For student projects, subdomains are almost always the cleaner choice.

Mistakes that waste evenings

  1. Apps bound to 0.0.0.0. Directly reachable on their ports, bypassing Nginx. Bind to 127.0.0.1 and close the app ports in the firewall.
  2. Missing proxy headers. Every visitor logs as 127.0.0.1 and generated links break. Copy the four proxy_set_header lines into every proxy block.
  3. Certbot failing. Almost always port 80 blocked by the firewall or DNS not yet propagated. Fix the plumbing, then re-run Certbot.
  4. WebSocket 400s. The Upgrade and Connection headers are missing. Add the block above.
  5. Reloading without testing. Always nginx -t before reload. A failed test tells you the exact line number.
  6. The default server block catching traffic. A request for a domain with no matching server_name falls into the default block. Either remove the default site or make it return 444 (drop the connection) so stray traffic doesn't land on a random app.

Frequently asked questions

Does Nginx replace my Node/Python app server?
No. Nginx is the receptionist, not the office. Your app still runs and does all the work; Nginx just routes, encrypts, and logs at the front door.

Can the frontend and backend share one domain?
Yes. Serve the built frontend files from Nginx at / and proxy /api/ to the backend. This also sidesteps most CORS headaches since everything comes from one origin.

What if my apps run in Docker?
Same pattern. Put the containers on a Docker network and proxy_pass to http://container-name:3000. Containers don't need published ports at all — Nginx is the only thing facing the internet.

Will Nginx slow my apps down?
Not measurably at student scale. Nginx handles tens of thousands of concurrent connections on modest hardware; your app will be the bottleneck long before Nginx is.

Do I need this for a single app?
Strictly, no — but it is still worth it. You get easy HTTPS, static-file caching, and clean logs, and adding a second app later becomes a five-minute job instead of a re-architecture.

Limitations

A reverse proxy is one machine's front door, not magic. It is a single point of failure — if the VPS goes down, everything behind it goes down too. All your apps share the VPS's CPU and RAM, so a runaway app can starve the others; that is a resource-planning problem, not something Nginx fixes. Each new subdomain needs DNS and certificate attention (wildcard certificates help here). And Nginx won't make a slow app fast — it routes and caches, but your code still has to perform.

More project guides

More in Web Development

Free SSL Certificates with Let's Encrypt: Certbot Setup, Auto-Renewal and Mixed-Content Fixes

How do you remove the browser's 'Not secure' warning from your project site for free? Install a Let's Encrypt certificate with Certbot - about fifteen minutes of work on a VPS, with automatic renewal. This guide covers the Nginx/Apache plugin setup, HTTP-01 vs DNS-01 vs webroot challenges, verifying auto-renewal with --dry-run, fixing mixed-content warnings after migration, and the real error messages (rate limits, NXDOMAIN, expired certificates) with fixes.

Read guide

Deploying a MERN Stack App on a VPS: Nginx, PM2, MongoDB, SSL and Surviving Reboots

Your MERN project works on localhost — now make it live. This guide deploys a MERN app on an Ubuntu VPS step by step: server hardening, Node.js and PM2, MongoDB with authentication, Nginx as a reverse proxy, free SSL with Certbot, environment variables done safely, and the PM2 startup setup that keeps your app alive across reboots. Includes the error table for the failures every student hits.

Read guide

Cheap Web Hosting in India for Students: Free Tiers, Shared Plans and VPS Compared

Where should a student project website actually live? This guide compares the three tiers that matter - free hosting (Netlify, Vercel, Cloudflare Pages), cheap Indian shared hosting (typically ~Rs 100-200/month first term), and VPS plans (typically ~Rs 400-800/month) - with a decision table by project type, the renewal-price trap to avoid, and a deployment checklist covering HTTPS, databases, and demo-day cold starts.

Read guide