Full run: the 3 commands
Rent a Hetzner server, start your own node and open the app with 3 commands.
Set up with AI: one prompt has Claude Code, Codex or Cursor run these 3 commands with you. Get the prompt.
Three commands rent a Linux server at Hetzner, start your own Snapchain read node on it, and open a web app on the same machine that shows your feed from that node. With an app key, the app also posts, likes and follows through your node (Post with an app key).
Vouchers do this Full run. FID, read node, snapshot, blockDelay, KEY_ADD, custody address, Caddy and sslip.io are in the Glossary.
1. Rent a server
Section titled “1. Rent a server”If the project already has a server named $NAME, or the volume $NAME-data, provision.sh stops and changes nothing. That server may be someone else’s. For a second node, pick another name (for example export NAME=snapchain-read-node-2) and keep it for teardown. To continue your own earlier run, use REUSE=1 ./provision.sh.
IP=$(./provision.sh) && echo "$IP"You will see [provision] server type … with the type it picked, then [provision] done. Next … and the server’s IP address. Time: under a minute.
Hetzner bills the server by the hour until you delete it. The tested type is cax31: with IPv4 and the 1000 GB volume, $0.164 an hour (our estimate, since the volume is listed by the month) and at most $102.29 a month. If a type isn’t available to rent in LOCATION, step 1 stops and lists the types that are, cheapest first. cpx42 (x86, 16 GB RAM) comes to $0.255 an hour, at most $159.29 a month. cpx42 has not been tested. What step 1 does: Server and node.
Our cax31 run cost $2.31 up to sync (14 h 06 m). After that it keeps costing $0.164 an hour, $3.93 a day, until you run CONFIRM="$NAME" ./teardown.sh (When you are done).
How the server type is picked
With SERVER_TYPE="", provision.sh tries cx43, cax31, cpx42 and ccx23 in that order and rents the first one for sale in LOCATION with at least 16 GB of RAM. It logs skipping … for each type it can’t rent there. If none is for sale, it stops with none of [...] is usable, lists the types you can rent there, and creates nothing. Run step 1 again with one of them in SERVER_TYPE, or with another place in LOCATION (fsn1, nbg1, hel1, ash, hil or sin).
To see the pick and the price first, without creating anything: ESTIMATE_ONLY=1 SERVER_TYPE="" VOLUME_GB=1000 ./provision.sh.
A trial server that deletes itself
Turn the watchdog on and set a price limit and the hours you expect to use:
IP=$(WATCHDOG=1 MAX_USD=8 EXPECTED_HOURS=24 ./provision.sh) && echo "$IP"provision.sh then starts watchdog.sh in the background on your computer, before it creates anything. It deletes the server, the volume and the firewall at the first of these:
- 24 hours after it started, even if the node is healthy (
WATCHDOG_MAX_WALL_SECSchanges this, for example172800for 48 hours), - when the bill would reach
MAX_USD, - when the node has made no progress for 2 hours.
Our cax31 sync took 14 h 06 m, so the 24-hour default leaves just under 10 hours with a synced node.
If the watchdog cannot start (for example HCLOUD_TOKEN is missing), step 1 stops with an error and creates nothing. Its output is in .provision-state/watchdog.out and its log in .provision-state/watchdog.log.
The progress check reads the database size and block heights over SSH with SSH_KEY_FILE, the private half of the key you uploaded to Hetzner. It defaults to ~/.ssh/id_ed25519 if that file exists. Without a key file only the price and time limits apply. If SSH stops working (for example your home IP changed), the watchdog logs a warning and keeps the server.
To keep the node after all, stop the watchdog without deleting anything: touch .provision-state/watchdog.disarm.
A 2 TB volume instead
Run step 1 as IP=$(VOLUME_GB=2000 ./provision.sh). It needs a Hetzner volume limit above 2000 GB and has not been tested. With cax31 it lists at $178.99 a month, $153.40 of that for the volume, or an estimated $0.286 an hour.
Show prompt
Context: I am following the RunFarcaster guide (3 commands: a Hetzner server running a Snapchain read node, and a web app that reads and posts through it). Repo: https://github.com/sudharshanx/runfarcaster (MIT). I cloned it to my computer (git clone https://github.com/sudharshanx/runfarcaster, then cd runfarcaster) and run every command from that folder. Scripts: provision.sh (step 1), node.sh (step 2), deploy-client.sh (step 3), teardown.sh, and watchdog.sh (optional). Server name: export NAME=snapchain-read-node (or another name I pick), in every terminal; all steps and teardown.sh use it. In any new terminal, get the server IP again with: IP=$(hcloud server ip "$NAME") Task: step 1, rent the server on Hetzner Cloud. 1. Check: hcloud version, jq --version, ssh -V, curl --version, and that HCLOUD_TOKEN is set without printing it: [ -n "$HCLOUD_TOKEN" ] && echo set. Run hcloud ssh-key list and ask me which key name to use. 2. Find my public IP: MY_IP=$(curl -4 -s https://ifconfig.me) and show it with echo "$MY_IP". If it is empty, stop. 3. Show the price, creating nothing: ESTIMATE_ONLY=1 SERVER_TYPE="" VOLUME_GB=1000 ./provision.sh Tell me which server type it picked and the price. If it says "none of [...] is usable", show me the types it lists and ask me which SERVER_TYPE (and LOCATION, if I want another place) to use. The guide has been tested on cax31 only. If it picked another type, tell me so, because other types have not been tested. 4. Use an empty Hetzner project. Set the name: export NAME=snapchain-read-node. 5. When I say yes, run (add LOCATION=<location> if I picked one): IP=$(SSH_KEY=<key-name> SERVER_TYPE="" VOLUME_GB=1000 WATCHDOG=0 SSH_SOURCE_CIDRS="$MY_IP/32" ./provision.sh) Expected: "[provision] cost guard OK", then "[provision] WATCHDOG=0: no watchdog started", then a "[provision] done. Next" line on stderr, and the IPv4 address on stdout. It takes under a minute. Known errors: "this project already has: ..." means a server, volume or firewall with that name exists and may be someone else's; nothing was changed. Ask me: another NAME for a new node, or REUSE=1 only if it is my own earlier run. "would pass the 1024 GB account quota" means the volume quota is full. "volumes size limit exceeded (resource_limit_exceeded)" means the account's volume limit (1024 GB for new accounts); keep VOLUME_GB=1000. "not available in <location> right now" or "none of [...] is usable" means those types aren't available to rent there; the error lists what is, cheapest first. "unknown LOCATION" lists the valid names. Tell me: the server keeps running, and Hetzner bills it by the hour, until I delete it with teardown.sh. If I say I only want a trial run: run step 1 without WATCHDOG=0 and with MAX_USD=8 EXPECTED_HOURS=24. Then provision.sh starts watchdog.sh, which deletes everything after 24 h, when the bill would pass MAX_USD, or after 2 h without progress. To keep the node after all: touch .provision-state/watchdog.disarm. Its log: .provision-state/watchdog.log. Rules for you: - Never ask for my recovery phrase (also called a seed phrase) or for any private key, and refuse one if I paste it. Tell me to stop, and to treat that account as exposed. - Never print, log or echo HCLOUD_TOKEN or any other token. Read it from the environment only. - Ask me before you run teardown.sh, delete anything, or change MAX_USD. - Show me real output only. If a command fails, show the exact error and stop.
2. Start the node
Section titled “2. Start the node”This uses $IP from step 1, in the same terminal.
ssh root@$IP \ "SNAPCHAIN_AGREE_NO_REWARDS=yes MIN_FREE_GB=900 bash -s" \ < node.shYou will see [node.sh] Node is in sync., then a health check with sync: SYNCED. Time: on a server with 8 cores and 16 GB of RAM (Hetzner cax31), loading the snapshot took 8 h 34 m in our run and catching up 5 h 15 m more. A faster disk or network shortens both. Times on cpx42 and other types have not been measured. What step 2 does: Server and node.
Right after step 1, SSH can refuse the connection for a minute while the server boots. Wait and run it again. On the first connection SSH asks you to confirm the server’s fingerprint; type yes.
If SSH drops, or to check progress
The node keeps going on the server. Reconnect and wait for it again:
IP=$(hcloud server ip "$NAME") # only in a new terminal, after export NAME=…ssh root@$IP 'bash -s wait' < node.shTo check progress:
ssh root@$IP 'curl -s 127.0.0.1:3381/v1/info' | jq '.shardInfos[] | {shardId, blockDelay}'Synced means every shard shows a blockDelay in single digits (Getting Started). The API does not answer while the snapshot loads.
Show prompt
Context: I am following the RunFarcaster guide (3 commands: a Hetzner server running a Snapchain read node, and a web app that reads and posts through it).
Repo: https://github.com/sudharshanx/runfarcaster (MIT). I cloned it to my computer (git clone https://github.com/sudharshanx/runfarcaster, then cd runfarcaster) and run every command from that folder. Scripts: provision.sh (step 1), node.sh (step 2), deploy-client.sh (step 3), teardown.sh, and watchdog.sh (optional).
Server name: export NAME=snapchain-read-node (or another name I pick), in every terminal; all steps and teardown.sh use it. In any new terminal, get the server IP again with: IP=$(hcloud server ip "$NAME")
Task: step 2, start the Snapchain read node on the server (1000 GB volume).
1. Use $IP from step 1. Only in a new terminal: IP=$(hcloud server ip "$NAME")
2. ssh root@$IP "SNAPCHAIN_AGREE_NO_REWARDS=yes MIN_FREE_GB=900 bash -s" < node.sh
If SSH says "Connection refused" right after step 1, the server is still booting: wait a minute and run it again.
node.sh installs Docker, installs two disk helpers because the disk is under 1500 GB, runs the official bootstrap and waits up to 24 h for sync.
Expected: "[node.sh] Data disk ... arming disk-guard and snapshot-space-saver.", then "[node.sh] ... SYNCING shard 0: height=... blockDelay=..." lines, then "[node.sh] Node is in sync." and a health check with "sync: SYNCED". The HTTP API does not answer while the snapshot loads; that is normal. On cax31, our run took 14 h 06 m from renting the server to synced.
If SSH drops, the node keeps going. Continue waiting with: ssh root@$IP 'bash -s wait' < node.sh
Check: ssh root@$IP 'curl -s 127.0.0.1:3381/v1/info' | jq '.shardInfos[] | {shardId, blockDelay}' (synced = every blockDelay in single digits). Port 3381 is closed to the internet, so check over SSH.
Repeated "InsufficientPeers" warnings that mention "read-node-peers" are fine while blockDelay stays in single digits. If blockDelay keeps growing, tell me: the validators may have stopped making blocks.
Rules for you:
- Never ask for my recovery phrase (also called a seed phrase) or for any private key, and refuse one if I paste it. Tell me to stop, and to treat that account as exposed.
- Never print, log or echo HCLOUD_TOKEN or any other token. Read it from the environment only.
- Ask me before you run teardown.sh, delete anything, or change MAX_USD.
- Show me real output only. If a command fails, show the exact error and stop.3. Open the app
Section titled “3. Open the app”./deploy-client.sh "$IP"You will see [deploy-client] OK: https://<name> (client) …. Open that address: the status at the top says Connected to your node. Time: under a minute with a warm npm cache. The first build takes longer.
Step 3 can run before step 2 finishes, in a second terminal, while the node is still loading its snapshot (set NAME and IP there as in Lost $IP?). The script then ends with [deploy-client] OK: app is up at https://<name>. The node isn't answering yet, and the app shows Node still starting until the node’s API answers.
To post, add an app key: Post with an app key. What step 3 does: Server and node.
To build your own app, bot or analytics job on the node, start with Build on your node.
Use your own domain
Point the domain at $IP and pass it as a second argument: ./deploy-client.sh "$IP" node.example.com. Without one, the script uses <ip-with-dashes>.sslip.io, a free DNS service run by someone else. That is fine for reading. To add a key with your recovery phrase, use the local copy from Post with an app key.
Show prompt
Context: I am following the RunFarcaster guide (3 commands: a Hetzner server running a Snapchain read node, and a web app that reads and posts through it). Repo: https://github.com/sudharshanx/runfarcaster (MIT). I cloned it to my computer (git clone https://github.com/sudharshanx/runfarcaster, then cd runfarcaster) and run every command from that folder. Scripts: provision.sh (step 1), node.sh (step 2), deploy-client.sh (step 3), teardown.sh, and watchdog.sh (optional). Server name: export NAME=snapchain-read-node (or another name I pick), in every terminal; all steps and teardown.sh use it. In any new terminal, get the server IP again with: IP=$(hcloud server ip "$NAME") Task: step 3, put the web app in front of the node. Needs Node.js 22.12+ (22.x), 24.x or 26+ on my computer. Run: ./deploy-client.sh "$IP" (a DNS name of mine that points at $IP can go as the 2nd argument; without it the script uses <ip-with-dashes>.sslip.io) Expected last line: "[deploy-client] OK: https://<name> (client) and https://<name>/v1/info (node, proxied)". Under a minute with a warm npm cache. It can run while the node is still loading its snapshot: then it ends with "[deploy-client] OK: app is up at https://<name>. The node isn't answering yet", and the app shows "Node still starting" until the node answers. Then I open the address. The status at the top should say "Connected to your node". If I started the watchdog in step 1, remind me that it deletes the node 24 h after step 1. To keep it: touch .provision-state/watchdog.disarm. If it fails: ports 80 and 443 must be open (provision.sh opens them). Look at: ssh root@$IP 'cd /opt/snapchain-client && docker compose logs caddy'. For a specific SSH key: SSH_OPTS="-i <keyfile>" ./deploy-client.sh "$IP". Rules for you: - Never ask for my recovery phrase (also called a seed phrase) or for any private key, and refuse one if I paste it. Tell me to stop, and to treat that account as exposed. - Never print, log or echo HCLOUD_TOKEN or any other token. Read it from the environment only. - Ask me before you run teardown.sh, delete anything, or change MAX_USD. - Show me real output only. If a command fails, show the exact error and stop.