If your home internet puts you behind CGNAT (see §16.6for the test), you can’t be reached directly from the StoaChain network no matter what you configure on your router. This chapter walks through the workaround: rent a small cloud VPS with a real public IP, install a tunnel between it and your home node, and advertise the VPS’s address as the node’s public face. Peers dial the VPS, packets are piped through the tunnel to your machine at home, chainweb-node responds back through the same tunnel. The network thinks the VPS is the node.
Cost: around 1 €/month with IONOS VPS XS+ (or 3–5 €/month with Hetzner, Contabo, OVH, etc.). Setup time: ~30 minutes once. Operationally stable — the tunnel auto-reconnects on any blip, both sides are managed by systemd.
Two legitimate use-cases
The VPS-tunnel pattern isn’t just for operators stuck behind CGNAT. Even if you have a perfectly good public IPv4 at home, this pattern unlocks a second capability: running multiple StoaChain nodes from a single internet connection. Each node behind its own VPS tunnel is network-distinct from the perspective of the StoaChain peer registry — different hostname, different IP, different p2p-port if you want. 10 VPS × 1 €/month = 10 separate nodes for 10 €/month total, run from one home rack or one beefy home server with several docker containers.
Use-case 1: CGNAT workaround
Your ISP puts you behind shared NAT (see §16.6) so inbound packets can’t reach you. One VPS fronts your one home node, peer traffic flows through.
Cost: 1 €/mo · Nodes: 1
Use-case 2: multi-node from one internet
You have good home internet AND a beefy home server (or several machines). Run N chainweb-node containers at home, each behind its own VPS — N separate peer identities, N separate registry entries, N separate Stoicism streams.
Cost: N × 1 €/mo · Nodes: N
Why multi-node makes sense economically: Ten nodes at home cost ~10 €/mo in VPS infrastructure. Running ten PHYSICAL nodes in ten different locations would cost ten separate home internet contracts (50–100 €/mo each). The VPS tunnel pattern compresses the infrastructure footprint by 50–100×. The hardware side still needs to be real — ten chainweb-node instances on one box need real NVMe + RAM + CPU to benchmark well. See §15 — Benchmarking for ServerScore math on shared-host configurations.
Sizing — Tunneler vs Tunnelee, two separate models
The Tunneler (the VPS) and the Tunnelee (the chainweb host behind it) have completely different bottlenecks. Sizing each as if it were the other is the most common mistake operators make when planning the topology.
Tunneler (VPS only runs frps)
Bottleneck: network bandwidth.frps is a pure TCP relay — it does not run chainweb. Its CPU and RAM cost are negligible (about 50 MB RAM total for ten tunnels, < 2 % of one vCore at steady state).
A €1/mo VPS with 512 MB RAM and a 100 Mbps line can host 10+ Tunnelees from a CPU/RAM standpoint. The ceiling is always the WAN link.
Tunnelee (runs chainweb)
Bottleneck: real hardware. chainweb-node uses 700 MB–1.5 GB RAM, 2–15 % of one core at steady state, and needs SSD/NVME for RocksDB. Storage commitment on top.
One Tunnelee per real machine in the simple case. Beefy hosts can run multiple chainweb instances in segregated containers (cgroups-isolated) for proportional reward.
Tunneler sizing — bandwidth-driven
Numbers below approximate steady-state. Initial-sync bursts from multiple Tunnelees catching up simultaneously will spike meaningfully higher; choose a tier with headroom if you plan to onboard several Tunnelees in close succession.
| VPS tier | Specs | Steady-state Tunnelees | Notes |
|---|---|---|---|
| €1/mo | IONOS XS+, 512 MB / 100 Mbps | 10–20 | Catch-up sync limited by 100 Mbps cap |
| €3/mo | Hetzner CX11, 4 GB / 1 Gbps / 20 TB/mo | 30–60 | Comfortable headroom even during bursts |
| €5/mo | Contabo S, 1 Gbps | 50–100 | Practical only with mixed-site Tunnelees |
Tunnelee sizing — chainweb-resource-driven
The Tunnelee is the box that actually runs chainweb. From production telemetry across our running fleet:
- RAM: 700 MB – 1.5 GB per chainweb instance. A 1 GB RAM box can’t run more than one; plan ≥ 2 GB free per instance.
- CPU: 2–15 % of one core at steady state. One chainweb instance fits comfortably on a single dedicated core.
- Disk: SSD or NVME mandatory; HDD is refused at install time. RocksDB random-read IOPS is the binding constraint.
- Storage commitment: 500 GB – 2 TB per instance, growing slowly as the chain extends.
Operators with strong hardware (≥ 8 logical CPUs and ≥ 16 GB RAM) can opt into segregated mode at install time, running 2 to N chainweb containers on one box with cgroups-enforced resource caps. Each container is scored independently. The install wizard detects strong hardware and offers this as a first-class option.
Why this works — the high-level idea
CGNAT breaks incoming connections because your ISP shares your IP with many neighbours and can’t route inbound traffic to a specific subscriber. But outbound traffic through CGNAT is fine— when your machine opens a connection to a server, the ISP’s NAT tracks that session and knows how to route replies back to you.
We exploit that: your home node makes an outbound connection to the VPS and holds it open permanently. The VPS has a real public IP, so it canreceive incoming connections from anyone. When a StoaChain peer tries to reach your node, the peer actually connects to the VPS. The VPS pipes all those bytes down the tunnel to your home node, and replies come back the same way. From the peer’s perspective nothing is unusual — it just connected to a normal chainweb node. From your ISP’s perspective, you have a single permanent outbound session to the VPS, which CGNAT is happy to let through.
StoaChain peer VPS (public IP) Your home node
anywhere (behind CGNAT)
│ │ │
│ ── dials :1789 ──▶ │ │
│ ┌───┴────┐ │
│ │ frp │ ◀── outbound TCP ────┤ frp
│ │ server │ (persistent) │ client
│ └───┬────┘ │
│ │ │
│ └─ raw TCP bytes ───▶ chainweb-node
│ (handles TLS, replies)
│ ◀── reply ─────── (back through the tunnel, VPS → peer)
What you need before starting
- A working StoaChain node at home (chainweb-node already installed). See §10 Technology stack if you’re not there yet.
- SSH access (as root or sudo) to both your home node and — once you rent it — the VPS.
- A DuckDNS account (free, takes 1 min to create — step 2 below).
- A payment method for the VPS provider (card or SEPA at IONOS; most EU providers accept both).
Step 1 — Rent a VPS
IONOS VPS XS+is the cheapest option that’s proven to work: 1 vCPU, 1 GB RAM, 10 GB NVMe, unmetered traffic at 500 Mbps, dedicated public IPv4. Price 1 €/monthat current promotional rates. That’s 12 €/year — a rounding error against what the tunnel enables.
Alternatives that also work, in case IONOS is unavailable in your region or you want something specific:
- Hetzner CX22 (previously CX11) — 4.51 €/month, 1 vCPU AMD, 2 GB RAM, 40 GB NVMe. Excellent network, EU datacenters.
- Contabo VPS 10 — 3.99 €/month, 4 vCPU, 6 GB RAM, 50 GB NVMe. Massively over-specced for a tunnel but cheap and reliable.
- OVH VPS Starter — 3.50 €/month, similar spec to Hetzner.
- Vultr, Linode, DigitalOcean — all have ~5 €/month tiers, globally distributed.
Any of them works — pick by: (a) proximity to where most StoaChain peers are (EU if you’re in EU; fewer ms of latency on the tunnel ≠ fewer ms peering latency), (b) whether they support Ubuntu 22.04 / 24.04 / 26.04 (all listed do), (c) price.
IONOS specifics (1 €/month path)
- Go to ionos.com/servers/vps and pick VPS XS+.
- Pick Ubuntu 26.04 (newest LTS, longest support runway) or Ubuntu 24.04 as the operating system.
- Skip any add-ons (backups, cloud-panel, etc.) — the VPS is a tunnel, it doesn’t need them.
- Register / log in, pay. IONOS provisions the VPS in ~5 minutes and emails you the root password + IP address.
- SSH in once to change the root password to something you pick (never leave the generated one):
ssh root@YOUR_VPS_IP, thenpasswd.
Step 2 — Set up DuckDNS
DuckDNS (duckdns.org) is a free dynamic-DNS service. We use it so our node has a stable hostname (not a raw IP) that chainweb advertises to peers, even though the underlying IP will eventually change (when the VPS is reprovisioned, or if you migrate to a different provider).
- Go to duckdns.organd sign in with any OAuth provider (GitHub, Google, Twitter — doesn’t matter, nothing is read except your identifier).
- In the “domains” input, pick a subdomain like
spartacus-ionut. That gives youspartacus-ionut.duckdns.orgfree forever. Click add domain. - On the same page, note your tokenat the top — a UUID-looking string. Save it; you’ll paste it into scripts later. Treat it like a password: whoever has it can change where your DNS points.
- Leave the “current ip” field blank for now. We’ll point it at the VPS in Step 6.
Step 3 — Open the right ports in the IONOS firewall
IONOS (and most cloud providers) has a second firewall layer at the virtual-infrastructure level, separate from UFW on the VM itself. Both must allow the ports we tunnel, or traffic is dropped before it reaches UFW.
- Log in to the IONOS Cloud Panel (
cloud.ionos.com). - Navigate to Network → Firewall Policies (or Firewall depending on your UI version).
- Select (or create) the firewall policy attached to your VPS.
- Add inbound TCP rules for:
22/tcp— SSH (forwarded to your home node)1789/tcp— chainweb P2P1848/tcp— chainweb service API7000/tcp— frp control channel (authenticated by token, only accepts the tunnel client)
- Source:
0.0.0.0/0(all IPv4) for 1789, 1848, 22 — peers arrive from anywhere. For 7000, you could restrict to your home ISP’s IP range if you want belt-and-braces, but since it’s token-authenticated, leaving it open to all is safe. - Save & apply.
Step 4 — Run the tunnel-server script on the VPS
SSH into the VPS as root:
ssh root@YOUR_VPS_IPThen download and run the installer. The script installs frp (an MIT-licensed, battle-tested reverse-proxy daemon used by thousands of operators), writes systemd units, configures UFW, and generates a random auth token:
wget https://ancientholdings.eu/scripts/setup-tunnel-vps.sh
chmod +x setup-tunnel-vps.sh
sudo ./setup-tunnel-vps.shSave the auth token it prints at the end. You’ll paste it on the home-node side in Step 5. It looks like a 32-char base64 string.
The script also prints the VPS’s public IP. If you’d rather confirm it yourself, run curl ifconfig.me.
Privacy check: the script is plain bash — review it before running. You can read it at /scripts/setup-tunnel-vps.sh on this site. Everything it does is documented line-by-line in comments. No external calls, no data uploaded anywhere except the frp binary download from the official github.com/fatedier/frp release.
Step 5 — Run the tunnel-client script on your home node
SSH into your home node (the one running chainweb-node):
ssh user@home-node-ip # or whatever your local address isThen:
wget https://ancientholdings.eu/scripts/setup-tunnel-node.sh
chmod +x setup-tunnel-node.sh
sudo ./setup-tunnel-node.sh YOUR_VPS_IP YOUR_AUTH_TOKENReplace YOUR_VPS_IP with the IP from Step 4, and YOUR_AUTH_TOKEN with the token the VPS script printed.
The script installs the frp client, configures it with the token, writes a systemd unit, enables + starts it. On success it prints “✓ tunnel established — control channel authenticated.”
If something goes wrong: journalctl -u frpc -f on the home node shows the client’s logs, journalctl -u frps -fon the VPS shows the server’s. Wrong token is the #1 cause — double-check the exact string.
Step 6 — Point DuckDNS at the VPS
Back on the DuckDNS page:
- Find your subdomain row (e.g.
spartacus-ionut.duckdns.org). - In the current ipfield, paste the VPS’s public IPv4 from Step 4. Click update ip.
- Verify from anywhere: run
nslookup spartacus-ionut.duckdns.org— should return the VPS IP.
DNS propagation is usually instant on DuckDNS (they have very short TTLs). If nslookup still returns the old IP, wait 60 seconds and retry.
Step 7 — Configure chainweb-node’s p2p-hostname
On the home node, chainweb-node must advertise the DuckDNS hostname (not the raw IP — the hostname keeps working even if the VPS gets a new IP someday):
- If you installed chainweb via the hub’s Install wizard, open the node’s page → Chainweb → Flags tab. Edit
p2p-hostnametospartacus-ionut.duckdns.org(your actual hostname). Tick “restart chainweb to apply” and save. - If you installed chainweb manually, edit the compose file / systemd unit / screen command to pass
--p2p-hostname=spartacus-ionut.duckdns.organd restart chainweb-node.
Step 8 — Get a TLS cert for the hostname
chainweb P2P rejects self-signed certs (peers reject “unknown CA”). You need a CA-signed cert whose CN matches your p2p-hostname. Let’s Encrypt via DNS-01 challenge works perfectly for DuckDNS hostnames.
Easiest path — use the hub: on the node’s page, the hub has an “Obtain Let’s Encrypt cert” button (Chainweb → Identity or Cert-rotate card). Choose DNS-01 via DuckDNS, paste the DuckDNS token from Step 2, click obtain. Hub runs certbot on the home node, fetches a cert valid for 90 days, places it where chainweb-node reads it. Auto-renews via certbot.timer.
Manual path — if you’re not using the hub yet:
sudo apt install certbot
# DNS-01 via DuckDNS (the hook script writes TXT record, waits,
# checks propagation, cleans up after).
sudo DUCKDNS_TOKEN=YOUR_DUCKDNS_TOKEN certbot certonly \
--manual --preferred-challenges dns \
--manual-auth-hook /usr/local/bin/duckdns-auth.sh \
--manual-cleanup-hook /usr/local/bin/duckdns-cleanup.sh \
-d spartacus-ionut.duckdns.org
# Or use the acme.sh tool which has DuckDNS built in:
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --issue --dns dns_duckdns \
-d spartacus-ionut.duckdns.orgThe cert files (fullchain.pem + privkey.pem) go into wherever chainweb-node reads them from. Default is --p2p-certificate-chain-file + --p2p-certificate-key-file — adjust paths in your chainweb config to match.
Step 9 — Register the node in the Ancient Holdings hub
On the hub (ancientholdings.eu/hub): go to Nodes → Add node. Fill:
- Host:
spartacus-ionut.duckdns.org(your DuckDNS hostname) - SSH port:
22 - SSH user: the user on the home node (not root usually; one with passwordless sudo)
- SSH password: for bootstrap (the hub installs its own keypair, then drops the password path)
- Owner email: your account email on the hub
The hub dials the DuckDNS hostname → hits the VPS → tunnels to your home node → authenticates, installs its key, drops the password, bootstraps. From that moment on, every hub operation (probe, benchmark, backup, control) rides the tunnel transparently.
Step 10 — Verify end-to-end
After everything is up, confirm from a third machine (not the home node, not the VPS — somewhere outside your LAN):
# TCP reachability
nc -zv spartacus-ionut.duckdns.org 1789
nc -zv spartacus-ionut.duckdns.org 1848
nc -zv spartacus-ionut.duckdns.org 22
# TLS handshake + cert inspection on chainweb's P2P port
openssl s_client -connect spartacus-ionut.duckdns.org:1789 \
-servername spartacus-ionut.duckdns.org < /dev/null 2>&1 \
| grep -E "(subject=|issuer=|verify return)"Expected output:
- All three
nc -zvcommands report “succeeded” — proving the VPS listens and the tunnel pipes through. subject=CN = spartacus-ionut.duckdns.org— cert CN matches hostname.issuer=C = US, O = Let’s Encrypt, CN = R3(or similar) — CA-signed, not self-signed.verify return:1— TLS handshake OK.
Then on the hub: the node’s status should flip from “unreachable” to green. First probe completes in a couple of seconds. Chainweb sync begins pulling peers within minutes. Stoicism warmup starts accruing once 24h of cut-tracking passes (see §6 Stoicism).
Troubleshooting
Tunnel client can’t connect to VPS
- Check VPS firewall (IONOS panel AND UFW on the VPS) both allow inbound 7000/tcp. A quick
nc -zv VPS_IP 7000from anywhere with internet will tell you. journalctl -u frpc -n 50on the home node shows the exact error.- Token mismatch is the most common: the server script prints it once at end, if you missed/mistyped it, re-read
/etc/frp/.tokenon the VPS (root-readable).
Peers connect but immediately drop / hub probe fails
- Most likely: cert doesn’t match hostname OR is self-signed. Run the openssl test from Step 10 and check CN + issuer.
- Or:
p2p-hostnamein chainweb config is still pointing at your home IP, not the DuckDNS name. Fix + restart.
Tunnel keeps disconnecting
- Your ISP’s CGNAT may be closing idle sessions. The frp config already sends keepalive every 7200s. If your ISP is more aggressive (some are 5-10 min), lower the keepalive: edit
/etc/frp/frpc.tomlon home node, settransport.dialServerKeepalive = 60,systemctl restart frpc. - Check home-node internet stability — CGNAT NAT tables are cleared on router reboot, so any reboot triggers a reconnect.
Hub says node is unreachable even though nc -zv works from outside
- Verify the hub’s host field matches what DNS resolves to. If you have multiple DuckDNS subdomains update, cache may be stale on the hub’s DNS resolver. Hub admin can
pm2 restart ancientholdingsto flush. - If you changed host on an existing node row, the node’s SSH key fingerprint may differ. Use “Re-seat SSH key” on the node’s Overview tab after change-host.
Operational notes
- If the VPS reboots(IONOS maintenance, you resize it, whatever) — the tunnel server restarts automatically (systemd), then the home node’s frp client reconnects within 5-10s. Peers may see a brief handful of seconds of peering interruption.
- If the home node reboots — frp client starts at boot (systemd enable), reconnects to VPS automatically. Chainweb-node starts, finds the tunnel already up, resumes sync.
- If the home ISP changes your CGNAT IP — nothing changes from the StoaChain network perspective. Your tunnel is an outbound session to the VPS; new ISP IP just means the outbound NAT mapping changes but the VPS still sees the same frp session.
- Monitoring:
journalctl -u frpc -fon home,journalctl -u frps -fon VPS, or enable the frp dashboard (commented out in the generated config — see/etc/frp/frps.tomlon the VPS). The hub’s normal node-probe also tells you whether things are working without looking at logs. - Rotating the auth token: edit
/etc/frp/frps.tomlon VPS and/etc/frp/frpc.tomlon home, set the new token on both,systemctl restart frps+ thensystemctl restart frpc. Do it if you suspect leak. - Tunnel cost: IONOS VPS XS+ = 1 €/month + VAT. Unmetered traffic (fair-use; chainweb P2P eats maybe a few GB/day at most — well within any fair-use cap). Cheaper options exist (Racknerd annual billing ~1 €/mo equivalent) but IONOS is EU-local and billed monthly.
Rolling back the tunnel
If you ever get a proper public IP at home and want to retire the VPS:
- On the home node:
sudo systemctl disable --now frpc; sudo rm /etc/systemd/system/frpc.service; sudo rm -rf /etc/frp - On the VPS: same with
frps, then cancel the VPS at the provider. - Point DuckDNS at your new home public IP.
- In chainweb-node config,
p2p-hostnamestays the same (it’s still the DuckDNS name) — just the underlying IP changed. - Set up port-forwarding on your router for 1789/1848 (external=internal — see §16.5).
- On the hub, use Change hoston the node’s Overview tab to switch from VPS IP to home public IP (or keep the DuckDNS hostname if you were using it — the host in the hub can be the hostname, same as before).
No state on the node itself changes — it’s the same chainweb-node with the same data, same benchmarks, same Stoicism. Only the network path to reach it changes.
Related
- §16.6 — Public IPv4 requirement (the problem this chapter solves)
- §10 — Technology stack (where chainweb-node fits)
- fatedier/frp on GitHub (the tunnel software — MIT license, ~80k stars)
- DuckDNS (free dynamic DNS)