Loading...

India's Largest Stationary Point for Imported Items
Journal

How to Master Calligraphy

A
admin
July 13, 20269 min read2 views
How to Master Calligraphy

I have 2 next js app and 1 nodeexpress app - http://72.60.219.181/ this is the ip address of the vps. ufw is also setup so u need to properly allow - for now will work with ip and then move to domain afterward - use apache reverse proxy also. do not touch 000defaultconfig - make config per file. backend port will be 8879 and the frontend will be 34321, 34322. assume github links of each for now - we will share later. now create the script to do all at once

Architected multi-app deployment strategy using Apache reverse proxy routing

Architected multi-app deployment strategy using Apache reverse proxy routing

Quick assumption before I hand this over: with just an IP (no domains yet), Apache can't tell apps apart by ServerName on the same IP — so I'm exposing each app on its own external port through Apache, proxying to the internal Node ports. Layout:

App

Internal (Node/PM2)

External (Apache)

Frontend 1 (Next.js)

34321

80 (main entry)

Frontend 2 (Next.js)

34322

8082

Backend (Express)

8879

8083

Node ports (34321/34322/8879) stay firewalled off from the internet — only reachable via localhost through Apache. When you get a domain, this collapses cleanly to ServerName-based vhosts on 80/443 with certbot — I've left notes for that.

I'll create the script now and read the relevant setup notes as I go.

Determined to deliver deployment script directly without embellishment

Determined to deliver deployment script directly without embellishment

Create working directory

Script

I need to write out the script directly without any additional commentary or formatting.

Create the all-in-one VPS deployment script for 2 Next.js apps + 1 Express backend behind Apache reverse proxy with UFW rules

setup-vps.sh

Validate bash syntax

Script

Copy final script to outputs

Script

Presented file

Done

Before running: edit the top CONFIG block — swap in your real GitHub repo URLs (REPO_FRONTEND1/2, REPO_BACKEND). Everything else has working defaults.

What it does, in order:

  1. Installs Node 20 LTS, PM2, Apache, UFW, git

  2. Opens UFW for SSH + ports 80, 8082, 8083, 443 (Node ports 34321/34322/8879 stay closed to the internet — only reachable via 127.0.0.1 from Apache)

  3. Enables proxy, proxy_http, proxy_wstunnel, headers, rewrite Apache modules

  4. Adds Listen 8082 / Listen 8083 to ports.conf (port 80 already listens by default)

  5. Clones each repo, runs npm ci + npm run build (frontends), npm ci (backend)

  6. Starts/restarts all three under PM2, pm2 saves, sets up boot persistence

  7. Writes three separate vhost files — frontend1.conf, frontend2.conf, backend.conf — under sites-available, a2ensites them, leaves 000-default.conf completely alone

  8. Runs configtest, reloads Apache, does a quick curl health check on all three

Result:

  • http://72.60.219.181/ → Frontend 1

  • http://72.60.219.181:8082/ → Frontend 2

  • http://72.60.219.181:8083/ → Backend API

It's safe to re-run (won't duplicate PM2 processes, Listen lines, or Apache modules).

Two assumptions baked in you should sanity-check against your actual repos:

  • Each package.json has a working start script (Next.js: next start, Express: reads process.env.PORT)

  • Your Express entrypoint actually reads PORT from env rather than hardcoding 8879 — if it's hardcoded that's fine too, just confirm it matches

One open question I didn't want to guess on:

Q: Does your Express backend read the port from process.env.PORT, or is 8879 hardcoded in the code? A: done in code- no need to worry about that

Good — hardcoded to 8879 in code, that's exactly what the script assumes internally, so nothing to change there. The PORT="${port}" pm2 start ... env var passed to the Express app will just be ignored (no harm), and Apache's backend.conf proxies straight to 127.0.0.1:8879 regardless.

Once you share the actual repo URLs, just drop them into the REPO_FRONTEND1 / REPO_FRONTEND2 / REPO_BACKEND variables at the top and run:

bash

chmod +x setup-vps.sh
sudo ./setup-vps.sh

If any step fails partway (e.g. build error in one app), it's safe to fix and re-run — PM2 restarts existing processes instead of duplicating them, and the Apache/UFW steps are idempotent too.

why 8082 and 8083? are u opening 80 and 443 completely ? exposing the whole vps. im sharing the current ufw. and i wanted [http://72.60.219.181:](http://72.60.219.181:8082/)32321 , [http://72.60.219.181:](http://72.60.219.181:8082/)34322 for frontends.

oot@srv1060125:/var/www/cly-backend-prod# ufw status Status: active To Action From -- ------ ---- 22 LIMIT Anywhere 443 ALLOW Anywhere 9878 DENY Anywhere 3000 DENY Anywhere 3001 DENY Anywhere 3300 DENY Anywhere 6555/tcp ALLOW Anywhere 7885/tcp ALLOW Anywhere 5666/tcp ALLOW Anywhere 80 ALLOW Anywhere 28471 DENY Anywhere 28472/tcp ALLOW Anywhere 28473/tcp ALLOW Anywhere 28474/tcp ALLOW Anywhere 28475/tcp ALLOW Anywhere 22 (v6) LIMIT Anywhere (v6) 443 (v6) ALLOW Anywhere (v6) 9878 (v6) DENY Anywhere (v6) 3000 (v6) DENY Anywhere (v6) 3001 (v6) DENY Anywhere (v6) 3300 (v6) DENY Anywhere (v6) 6555/tcp (v6) ALLOW Anywhere (v6) 7885/tcp (v6) ALLOW Anywhere (v6) 5666/tcp (v6) ALLOW Anywhere (v6) 80 (v6) ALLOW Anywhere (v6) 28471 (v6) DENY Anywhere (v6) 28472/tcp (v6) ALLOW Anywhere (v6) 28473/tcp (v6) ALLOW Anywhere (v6) 28474/tcp (v6) ALLOW Anywhere (v6) 28475/tcp (v6) ALLOW Anywhere (v6) 53 ALLOW OUT Anywhere 80 ALLOW OUT Anywhere 443 ALLOW OUT Anywhere 53 (v6) ALLOW OUT Anywhere (v6) 80 (v6) ALLOW OUT Anywhere (v6) 443 (v6) ALLOW OUT Anywhere (v6)

like the custom ports are live - make the ones u are making also do the same.

Show more

Recalibrated port mapping strategy and clarified firewall exposure scope

Recalibrated port mapping strategy and clarified firewall exposure scope

Good questions — let me clear up both things and fix the port mapping to match what you actually want.

On "opening 80/443 completely exposes the whole VPS" — that's not quite how UFW works. ufw allow 80/tcp only opens port 80 specifically; every other port stays under your default-deny policy (as your own ruleset shows — you've got explicit DENY lines for 9878, 3000, 3001, 3300, 28471 sitting right next to ALLOW lines for others). Opening a port ≠ opening the box. That said — you're right that I shouldn't have touched 80/443 at all here: they're already allowed in your live config (presumably serving something else already, maybe behind an existing vhost), and I have no business re-touching rules that already exist or routing a new app through a port that might already be spoken for. I've dropped port 80 entirely from this task.

On the port scheme — you want 34321 and 34322 to be the actual public URLs, matching how your other custom ports (6555, 7885, 28472-75) work: UFW allows that exact port, direct hit. The one wrinkle: since you asked for an Apache reverse proxy in front (not raw Node exposed), Apache has to be the one bound to 34321/34322/8879 externally — Node can't also bind those same numbers, or you'll get EADDRINUSE. So Node moves to a different internal-only port, Apache takes the public number. Functionally identical to what you see with your other ports; Apache is just quietly in the middle for headers/logging/future SSL.

New mapping:

App

Public (UFW + Apache listens here)

Internal (Node/PM2, loopback only)

Frontend 1

34321

44321

Frontend 2

34322

44322

Backend

8879

18879

I also removed the redundant ufw allow 22/tcp and ufw --force enable calls — your SSH rule is already rate-limited (LIMIT), and re-adding a plain allow would just add a weaker duplicate rule sitting next to it. Script now only touches the 3 new ports.

Reconsidered port configuration to align with existing firewall rules

Reconsidered port configuration to align with existing firewall rules

Now update the vhost templates (all three now use the same non-default-port pattern, and internal PM2 ports changed) and the summary/health-check section:

Edited 3 files

Edited 3 files

Now the closing summary section — update to reflect no port 80/443 involvement and the new mapping:

Update closing summary to reflect new port scheme and no 80/443 touch

Update closing summary to reflect new port scheme and no 80/443 touch

Now let's verify syntax and the health-check section still references the right variables, then re-copy to outputs:

Identified port conflict and pivoted to direct firewall exposure strategy

Identified port conflict and pivoted to direct firewall exposure strategy

One more important fix — you told me the backend's port 8879 is hardcoded in the Express code, not read from an env var. That changes things: Node will bind directly to 8879 itself, which means Apache can't also bind 8879 to proxy to it — same port, same host, straight conflict.

So for the backend, I'll drop Apache out of the loop entirely and expose it the same way your other raw ports (6555, 7885 etc.) already work: Node binds 8879 directly, UFW allows 8879 straight through. No reverse proxy needed since there's nothing to remap. The two Next.js frontends keep the Apache-proxy setup since PM2 can freely pass -p <port> to next start.

Edited 3 files

Edited 3 files

Now update the Apache Listen/vhost sections to drop the backend entirely, and the PM2 start function to not force a -p port for the backend:

Edited 2 files, viewed a file

Edited 2 files, viewed a file

Now remove the backend.conf Apache vhost entirely and drop its a2ensite call:

Viewed 2 files, edited a file

Viewed 2 files, edited a file

Now update the health-check and summary sections to reflect the direct backend exposure:

Edited 2 files

Edited 2 files

Let's do a final syntax check and re-copy to outputs:

Final syntax check and ensure no stale EXT_PORT_BACKEND references remain

Final syntax check and ensure no stale EXT_PORT_BACKEND references remain

Clean — no stale references left. Copying the corrected version over:

Ran a command, read a file

Ran a command, read a file

Final scheme:

App

Public URL

How

UFW rule added

Frontend 1

http://72.60.219.181:34321/

Apache proxy → Node 127.0.0.1:44321

34321/tcp

Frontend 2

http://72.60.219.181:34322/

Apache proxy → Node 127.0.0.1:44322

34322/tcp

Backend

http://72.60.219.181:8879/

Direct — Node binds 8879 itself

8879/tcp

Untouched: SSH (22, still LIMIT), port 80, port 443, 000-default.conf, and every other existing rule in your ufw list.

Only remaining assumption to flag: the backend rule above (open_port 8879) assumes your Express app binds to 0.0.0.0:8879 (all interfaces), not 127.0.0.1:8879. If it's bound to loopback only in code, opening the UFW port won't be enough to reach it externally — that'd need a code change, not a firewall one. Worth a quick check on your end before you run this.

Setup vps

SH 

now show me the config u wrote - just give as a reply i want to see any one

Resolved to exhibit a sample configuration file

Resolved to exhibit a sample configuration file

Here's the frontend1.conf it generates (Apache reverse-proxy vhost for the first Next.js app, public port 34321 → internal Node port 44321):

apache

# frontend1.conf -- Next.js app 1 (public port 34321 -> internal 44321)
<VirtualHost *:34321>
    ServerName 72.60.219.181

    ProxyPreserveHost On
    ProxyRequests Off

    # WebSocket upgrade support (harmless if unused)
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*) ws://127.0.0.1:44321/$1 [P,L]

    ProxyPass / http://127.0.0.1:44321/
    ProxyPassReverse / http://127.0.0.1:44321/

    ErrorLog ${APACHE_LOG_DIR}/frontend1_error.log
    CustomLog ${APACHE_LOG_DIR}/frontend1_access.log combined
</VirtualHost>

frontend2.conf is identical in structure, just port 34322 → 44322, and separate log filenames. The script writes each of these to /etc/apache2/sites-available/ and enables them with a2ensite000-default.conf never gets touched. Backend has no vhost file at all, since it's exposed directly rather than proxied.

How to Master Calligraphy | Cursive Letters