Self-Hosted ar.io Gateway on Hetzner Cloud

NOTE (2026-06-07): This post describes the initial Hetzner Cloud setup. Hetzner was subsequently replaced with a no-KYC privacy VPS (Servury) because Hetzner requires KYC/AML identity verification. The current process is documented in tutorial-privacy-vps-ario-gateway.md.


archerships.com is deployed to the Arweave permanent web. Content lives on chain permanently – but serving it to browsers requires a gateway, and the default gateway (arweave.net) runs on CDN77 infrastructure that has periodic multi-hour outages. When CDN77 goes down, the site returns HTTP 572 and is unreachable.

The fix is a self-hosted ar.io gateway node. The node caches your content locally, falls back to arweave.net on cache miss, and serves requests from your own server. Your site stays up even when arweave.net is having a bad day.

This guide uses Hetzner Cloud’s CAX21 ARM instance (~$5/month). If you prefer Oracle Cloud Always Free, the setup is identical after the instance is running – see the companion Oracle provisioning guide.


Architecture

Browser
  |
  v
Cloudflare (DNS proxy + CDN edge)
  |
  v
Hetzner CAX21 -- nginx (TLS termination)
  |
  v
ar.io node (Docker, localhost:3000)
  - serves content from local cache
  - fetches from arweave.net on cache miss
  - resolves ARNS names (e.g. archerships -> manifest txId)

1. Provision the Server

1.1 Create a Hetzner account

hetzner.com/cloud – payment by card or PayPal. For XMR payment see the companion essay on Monero-accepting VPS providers; most of those providers support the same Docker-based setup below.

1.2 Install the hcloud CLI

brew install hcloud

1.3 Create a project and API token

  1. console.hetzner.cloud > New project > name it (e.g. archerships)
  2. Project > Security > API Tokens > Generate API Token (Read & Write)
  3. Copy the token – it is shown only once

1.4 Configure the CLI

hcloud context create archerships
# Paste your API token when prompted

1.5 Add your SSH key

hcloud ssh-key create --name macbook --public-key-from-file ~/.ssh/id_ed25519.pub

1.6 Create the server

hcloud server create \
  --name ar-io-gateway \
  --type cax21 \
  --image ubuntu-24.04 \
  --ssh-key macbook \
  --location ash

cax21 is the ARM instance: 4 vCPU, 8 GB RAM, 80 GB SSD. Cost: ~$5/month. Location options: ash (Ashburn VA), fsn1 (Falkenstein DE), hel1 (Helsinki FI).

The command prints the server’s public IP. Note it.

1.7 Open firewall ports

hcloud firewall create --name gateway-fw
hcloud firewall add-rule gateway-fw --protocol tcp --port 22  --source-ips 0.0.0.0/0 --direction in
hcloud firewall add-rule gateway-fw --protocol tcp --port 80  --source-ips 0.0.0.0/0 --direction in
hcloud firewall add-rule gateway-fw --protocol tcp --port 443 --source-ips 0.0.0.0/0 --direction in
hcloud firewall apply-to-resource gateway-fw --type server --server ar-io-gateway

2. Server Setup

ssh root@<server-ip>

2.1 System update and packages

apt update -y && apt upgrade -y
apt install -y curl git nginx sqlite3 build-essential ufw

2.2 Firewall (ufw)

ufw allow 22 && ufw allow 80 && ufw allow 443 && ufw enable

2.3 Docker

apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Verify: docker run hello-world


3. DNS Configuration

In Cloudflare for your domain:

The wildcard (*) record is needed for ARNS subdomain resolution (e.g. archerships.yourdomain.com).

Wait 2 minutes, then verify:

curl -sI http://yourdomain.com | head -3

4. TLS Certificates

Using a Cloudflare Origin Certificate (15-year validity, no renewal needed):

  1. Cloudflare > SSL/TLS > Origin Server > Create Certificate
  2. Hostnames: yourdomain.com, *.yourdomain.com
  3. Key type: RSA 2048, validity: 15 years
  4. Download the certificate and private key

On the server:

mkdir -p /etc/ssl/yourdomain
nano /etc/ssl/yourdomain/cert.pem    # paste certificate
nano /etc/ssl/yourdomain/key.pem     # paste private key
chmod 600 /etc/ssl/yourdomain/key.pem

Set Cloudflare SSL/TLS mode to “Full (strict)”.


5. ar.io Node

5.1 Clone the repository

cd /var/lib
git clone -b main https://github.com/ar-io/ar-io-node ar-io-node
cd ar-io-node

5.2 Configure .env

nano .env

Paste (replace yourdomain.com with your actual domain):

ARNS_ROOT_HOST=yourdomain.com
GRAPHQL_HOST=arweave.net
GRAPHQL_PORT=443
START_HEIGHT=1580000
TRUSTED_GATEWAYS_URLS={"https://arweave.net": 1}

START_HEIGHT skips syncing all 1.6M+ historical Arweave blocks. The node fetches and caches content on first request, falling back to arweave.net on cache miss. A high START_HEIGHT means the node cannot join the ar.io network as a verified gateway – that is fine for personal use.

5.3 Start the node

docker compose up -d
docker compose logs -f --tail=30

Expected output: block sync progress, no ERROR lines. Wait until sync reports a recent block height before proceeding.

5.4 Enable auto-restart on boot

systemctl enable docker

The compose services use restart: unless-stopped by default.


6. nginx Configuration

nano /etc/nginx/sites-available/yourdomain

Paste (replace yourdomain throughout):

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com *.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name yourdomain.com *.yourdomain.com;

    ssl_certificate     /etc/ssl/yourdomain/cert.pem;
    ssl_certificate_key /etc/ssl/yourdomain/key.pem;

    proxy_read_timeout 120s;
    proxy_connect_timeout 10s;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
    }
}

Enable and reload:

ln -s /etc/nginx/sites-available/yourdomain /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && service nginx restart

7. Smoke Test

# Home page
curl -sI https://yourdomain.com/ | head -5
# Expected: HTTP/2 200

# ARNS subdomain (replace "archerships" with your ARNS name)
curl -sI https://archerships.yourdomain.com/index.html | head -5

The first request to a path may be slow (1-5s) while the node fetches and caches content from arweave.net. Subsequent requests are served from the local cache with no CDN77 dependency.


8. Keeping the Node Updated

ar-io-node releases approximately every few weeks:

cd /var/lib/ar-io-node
git pull origin main
docker compose pull
docker compose up -d

Optional weekly cron (runs at 4am Monday):

(crontab -l 2>/dev/null; echo "0 4 * * 1 cd /var/lib/ar-io-node && git pull -q && docker compose pull -q && docker compose up -d -q 2>&1 | logger -t ar-io-update") | crontab -

9. Disk Management

The node caches Arweave data in /var/lib/ar-io-node/data/. With a recent START_HEIGHT, initial growth is slow. Check periodically:

du -sh /var/lib/ar-io-node/data/
df -h /

If the 80 GB SSD fills up, add a Hetzner volume via CLI:

hcloud volume create --name ar-io-data --size 100 --server ar-io-gateway --automount --format ext4

Then symlink or move /var/lib/ar-io-node/data to the new mount point.


10. Rollback

To revert to arweave.net CDN (e.g. while debugging the node):

In Cloudflare, change the A record for @ back to a CNAME pointing to yourname.arweave.net. The Arweave content and ARNS record are unchanged; the switch is purely DNS and takes effect within seconds.


Hetzner vs. Oracle Always Free

Oracle’s Always Free A1 tier (4 OCPU, 24 GB RAM, 200 GB storage) has better specs than the Hetzner CAX21 and costs $0/month. The catch: Oracle Phoenix Ampere capacity is frequently exhausted (“Out of host capacity”) and can take days or weeks to open up. Hetzner CAX21 provisions in under a minute.

For a personal gateway the 8 GB RAM on Hetzner is sufficient. If you need the extra RAM headroom or want $0/month, Oracle is worth the wait.


Want to stay in touch?

If you’d like to support my work:

If there is a topic you’d like me to cover, please let me know!

Questions, comments, and suggestions are welcome.