The Article
Introduction
A few weeks ago, I started an ambitious project: MyZubster – a skills exchange platform with Monero payments, designed to be private and self-hosted.
Everything was great, until I tried to put it online. What should have been a simple deployment turned into a three-day journey through DNS, Nginx, systemd, Tor, and a firewall that seemed to have a mind of its own.
This post is the story of that journey. I hope it helps anyone trying to deploy a Node.js application on a VPS, especially if they encounter seemingly inexplicable errors.
The Starting Point
I had:
A Node.js application (Express + MongoDB) ready.
A VPS on Aruba Cloud with Ubuntu 24.04.
A domain (myzubster.com) managed with Cloudflare.
A dream (and very little sleep).
The goal was simple: make the application accessible via HTTPS, serve a React frontend, and add a Tor onion service for privacy-conscious users.
Problem #1: DNS Wasn't Resolving as Expected
The symptom:
ping myzubster.com returned a Cloudflare IP, but the domain wasn't reachable.
The diagnosis:
The nameservers were pointed to Cloudflare, not Aruba. All DNS changes made on Aruba's panel were useless.
The solution:
I checked the nameservers with dig NS myzubster.com.
I updated the DNS records directly on Cloudflare.
I set an A record for @ and www with the VPS IP.
Lesson learned:
DNS is like a phonebook. If the number isn't in the right directory, you're not calling anyone.
Problem #2: Nginx Returned 502 Bad Gateway
The symptom:
The browser showed 502 Bad Gateway.
curl -I https://myzubster.com returned 502.
The diagnosis:
Nginx was configured to proxy traffic to 127.0.0.1:4000, but my Node.js application was listening on 3000.
The solution:
I changed proxy_pass in the Nginx config from 4000 to 3000.
bash
sed -i 's/proxy_pass http:\/\/127.0.0.1:4000;/proxy_pass http:\/\/127.0.0.1:3000;/' /etc/nginx/sites-available/myzubster
systemctl reload nginx
Lesson learned:
Nginx isn't a mind reader. If you change your app's port, you have to tell it.
Problem #3: The Node.js App Wasn't Starting Automatically
The symptom:
After every server reboot, the app had to be restarted manually.
The solution:
I created a systemd service for Node.js.
File /etc/systemd/system/myzubster-gateway.service:
ini
[Unit]
Description=MyZubster Gateway
After=network.target mongod.service
Wants=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/MyZubsterGateway
EnvironmentFile=/root/MyZubsterGateway/.env
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Then:
bash
systemctl daemon-reload
systemctl enable myzubster-gateway
systemctl start myzubster-gateway
Lesson learned:
Don't use nohup in production. Use systemd – it's more robust and self-healing.
Problem #4: The React Frontend Wasn't Being Served
The symptom:
The React build was failing with import errors (missing react-router-dom, axios, etc.).
The solution:
I installed all missing dependencies and rebuilt:
bash
cd ~/myzubster-frontend
npm install react-router-dom axios @tailwindcss/forms @tailwindcss/typography
npm run build
cp -r dist/* /var/www/html/
Then I configured Nginx to serve the static files and handle client-side routing (all requests go to index.html).
Lesson learned:
React isn't a simple HTML file. It needs dependencies and proper routing configuration.
Problem #5: The Tor Onion Service Wasn't Responding
The symptom:
The onion link (http://...onion) returned "Unable to connect".
The diagnosis:
Tor wasn't configured correctly. The hidden service directory had incorrect permissions.
The solution:
I fixed the permissions and enabled the tor@default service:
bash
chown -R debian-tor:debian-tor /var/lib/tor/myzubster/
systemctl enable tor@default
systemctl start tor@default
I also configured Nginx to respond to the onion hostname without redirecting to HTTPS.
Lesson learned:
Tor is powerful, but it doesn't forgive permission errors or misconfiguration.
Problem #6: Tor Traffic Wasn't Reaching the Server
The symptom:
curl from the server for the onion service returned HTML 200, but Tor Browser showed a blank page or connection error.
The diagnosis:
Aruba (the hosting provider) was blocking traffic from Tor exit nodes at the infrastructure level. The server was responding correctly, but the traffic never arrived.
The evidence:
curl -H "Host: onion" http://localhost:80 returned the full HTML.
tail -f /var/log/nginx/access.log showed no incoming requests from Tor.
Check-host.net confirmed the site was reachable from all over the world.
The solution (ongoing):
I opened a support ticket with Aruba, providing all the technical details and logs, and requested that they whitelist Tor exit nodes or remove the infrastructure-level block.
Lesson learned:
Sometimes the problem isn't your code, your server, or your config. Sometimes it's the network you're hosted on. And sometimes you can't fix it – you just have to ask nicely.
What We Achieved Despite Everything
Despite the obstacles, here's what we built:
Component Status
Backend ✅ Node.js + Express on port 3000, served via systemd
Frontend ✅ React + Tailwind, built and copied to /var/www/html/
Nginx ✅ Reverse proxy with SSL, serving both clearnet and onion
DNS ✅ Cloudflare A record pointing to VPS IP
Tor onion ✅ Active and responding internally (curl test passes)
CI/CD ✅ Git with SSH for version control
Documentation ✅ Two Dev.to articles published
Aruba ❌ Blocking Tor traffic (ticket in progress)
Final Thoughts
This journey taught me more than any tutorial ever could:
DNS is the root of all evil – or at least most of it. Always check your nameservers.
Nginx is a faithful servant – but only if you tell it exactly what to do.
systemd is your friend – use it, love it, never go back to nohup.
Tor is powerful, but unforgiving – permissions and config matter.
Providers can be your worst enemy – sometimes the problem isn't your server, it's the network you're on.
Persistence pays off – each error brought us closer to the solution.
The server is ready. The code is ready. The onion is ready. Now we're just waiting for Aruba to open the gates.
🔗 Links
Live site (clearnet): https://myzubster.com
Tor onion: http://olqcnbdlt35k2stmmwvzhvuetu2fc4us2jnn5wg6y6wlcddihfmdomid.onion
GitHub: https://github.com/DanielIoni-creator/MyZubsterGateway
Previous article (vision): MyZubster: The Open-Source Platform That Could Change the Financial Era
Previous article (deployment guide): From Zero to Production: Deploying a Node.js App with Nginx, Cloudflare, systemd, and Tor
💬 Final Message
If you're struggling with deployment, remember: you're not alone. Every error is a step forward. Every 502, 404, and connection refused is just a signpost pointing to the solution.
Keep going. The internet will thank you. 🚀
Top comments (0)