Skip to main content
Docs/Troubleshooting

Domain troubleshooting

VersionGate can report the engine as healthy while the hostname does not open in a browser. PM2 online, preflight DNS, and the dashboard status badge are not the same as public reachability.

What "working" actually means

SignalWhat it provesWhat it does not prove
pm2 status onlineversiongate-api and versiongate-worker are runningNginx, DNS, or TLS are correct
Preflight DNS AThis VPS resolved PUBLIC_DOMAINYour laptop or ISP can resolve the same name
Systems OperationalDashboard chrome renderedNothing — that badge is not a live health check
Certbot successLet's Encrypt issued a cert and nginx listens on 443Every resolver has the A record, or IPv6 is configured

Split the problem into layers

Work from the process outward. Fix the first layer that fails before changing the next one.

LayerTestPass condition
01 Enginecurl -sI --max-time 5 http://127.0.0.1:9090/HTTP from Bun on 9090
02 Nginx loopbackcurl -sI --max-time 5 http://127.0.0.1/Nginx proxies to 9090
03 Host headercurl -sI --max-time 5 -H "Host: YOUR_DOMAIN" http://127.0.0.1/Same 200 for the real hostname
04 TLS loopbackcurl -sIk --max-time 5 --resolve YOUR_DOMAIN:443:127.0.0.1 https://YOUR_DOMAIN/Cert + 443 on this box
05 Public portsss -lntp | grep -E ':80|:443|:9090'nginx on 80/443, bun on 9090
06 DNS splitdig +short YOUR_DOMAIN A && dig +short YOUR_DOMAIN A @8.8.8.8Both return this server IPv4

Diagnostic block

Replace YOUR_DOMAIN with the hostname in PUBLIC_DOMAIN. Do not leave the placeholder — dig YOUR_DOMAIN looks up a name that does not exist.

terminal
DOMAIN=YOUR_DOMAIN
grep -E 'PUBLIC_DOMAIN|PUBLIC_URL|NGINX_CONFIG_PATH|COOKIE_SECURE' ~/VersionGate/.env
ss -lntp | grep -E ':80|:443|:9090'
ls -la /etc/nginx/sites-enabled/
grep -RIn "server_name\|listen " /etc/nginx/sites-available /etc/nginx/sites-enabled /etc/nginx/conf.d

dig +short "$DOMAIN" A
dig +short "$DOMAIN" A @8.8.8.8

curl -sI --max-time 5 http://127.0.0.1/
curl -sI --max-time 5 -H "Host: $DOMAIN" http://127.0.0.1/
curl -sIk --max-time 5 --resolve "$DOMAIN:443:127.0.0.1" "https://$DOMAIN/"
Do not curl the public IP from the VPS
On Proxmox, NAT VPS, or a host that recently moved from a shared port to a dedicated port, curl http://$PUBLIC_IP/ often hangs. That is hairpin NAT: the guest cannot connect out to its own public address. Use loopback plus a Host header, or test 80/443 from a laptop. SSH working on a dedicated port does not by itself prove 80/443 are forwarded.

DNS: server says yes, browser says no

Preflight and dig on the VPS use the server resolver. Your browser uses the laptop or router resolver. Those can disagree for hours after you add vg.henry.example.com as a nested subdomain.

laptop
nslookup YOUR_DOMAIN
nslookup YOUR_DOMAIN 8.8.8.8

If the first command is NXDOMAIN and the second returns this server IPv4, the engine is fine. Point the laptop or router at 8.8.8.8 or 1.1.1.1, flush the local cache, and open https://YOUR_DOMAIN.

On Cloudflare: A record to the VPS IPv4, DNS only (grey cloud) until HTTP-01 succeeds. A parent name such as henry.example.com with no A record, or a broken CNAME, makes vg.henry.example.com fail on some resolvers. Do not add AAAA unless nginx also listens on [::]:443.

Three nginx files that fight each other

After install, Settings → Write nginx, Certbot, and a provider OS reset, these paths often all claim port 80:

PathWho writes itKeep?
/etc/nginx/sites-available/versiongateinstall.sh — often still server_name $PUBLIC_IPDisable the sites-enabled symlink once conf.d has the TLS vhost
/etc/nginx/conf.d/versiongate.confSetup wizard + certbot --nginxYes — this is the live dashboard vhost
/etc/nginx/conf.d/upstream.confSettings Write nginx (NGINX_CONFIG_PATH) and TrafficServiceupstream { } only — never a server { } block

Default NGINX_CONFIG_PATH is /etc/nginx/conf.d/upstream.conf. Settings → Write nginx config writes a server block there, then the next production deploy overwrites that file with an upstream block. The dashboard reports success; the installer vhost on the IP never changes.

cleanup
cp -a /etc/nginx/conf.d /root/nginx-conf.d.bak.$(date +%Y%m%d-%H%M)
cp -a /etc/nginx/sites-available/versiongate /root/nginx-sites-versiongate.bak
rm -f /etc/nginx/sites-enabled/versiongate
cat > /etc/nginx/conf.d/upstream.conf <<'EOF'
# Written by VersionGate on production deploy/promote
# Do not put server { } blocks in this file.
EOF
nginx -t && systemctl reload nginx
After cleanup
Do not click Settings → Write nginx config again until NGINX_CONFIG_PATH points at the real vhost (or the product writes site and upstream to different files). Removing the IP-only site makes http://SERVER_IP/ return 404; use the hostname over HTTPS.

Shared port, dedicated port, OS reset

A host that started on a shared/NAT port and later received a dedicated public port still sits behind the provider hypervisor (often Proxmox). Two OS resets reinstall VersionGate and stack another copy of the three nginx files above. SSH on the dedicated port only proves that port is forwarded. Confirm 80 and 443 from a machine that is not the VPS.

from a laptop
curl -sI --max-time 10 http://SERVER_IP/
curl -sI --max-time 10 --resolve YOUR_DOMAIN:443:SERVER_IP https://YOUR_DOMAIN/

Dashboard domain vs app URL

The dashboard hostname is PUBLIC_DOMAIN. Deployed apps are not separate vhosts. Open them at https://YOUR_DOMAIN/p/<project>/<environment>. Custom app hostnames such as app.example.com are not written by the engine today.