DEV Community

Daniel Ioni
Daniel Ioni

Posted on

The Long Night of Deployment: How We Tamed DNS, Nginx, Tor, and a Rebel Firewall

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).
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💬 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)