Deploy Pinchy on Hetzner Cloud
Hetzner Cloud is an excellent choice for running Pinchy. Their servers run in EU data centers (Germany and Finland), which keeps your data under EU jurisdiction — no overseas transfers, no third-party cloud AI providers touching your conversations. Combined with strong price-to-performance ratios, it’s the go-to option for European teams that take data sovereignty seriously.
Why Hetzner for self-hosted AI agents?
Section titled “Why Hetzner for self-hosted AI agents?”- EU data centers — Falkenstein, Nuremberg, Helsinki. Your AI conversations stay in the EU.
- Price — A server with 4 GB RAM and 2 vCPUs costs under €4/month (including IPv4). Enough to run Pinchy with a small team.
- Predictable pricing — No surprise bandwidth charges. 20 TB outbound traffic included.
- ISO 27001 certified — Relevant if your compliance team asks where the servers are.
What you’ll need
Section titled “What you’ll need”- A Hetzner Cloud account (free to create, pay-as-you-go)
- A domain name (optional for testing, required for production HTTPS)
- An LLM provider API key (Anthropic, OpenAI, or Google) — you’ll enter this in Pinchy’s setup wizard
Create your server
Section titled “Create your server”-
Open the Hetzner Cloud Console
After signing up or logging in at accounts.hetzner.com, click the grid icon (⊞) in the top-right corner and select Console. This is where you manage your servers.
-
Create or select a project
You’ll see your projects on the dashboard. Create a new one (e.g., “Pinchy”) or click an existing project to open it.
-
Start creating a server
Inside your project, click the red Create Resource button in the top-right corner and select Servers.
-
Choose server type
Under Shared Resources, select Cost-Optimized, then pick x86 (Intel/AMD) as the architecture.
Scroll down to the server sizes and select the smallest option with at least 4 GB RAM and 2 vCPUs. This is the minimum for Pinchy.
-
Choose a location
Pick any location — all are fine for Pinchy:
- Falkenstein or Nuremberg (Germany)
- Helsinki (Finland)
- Ashburn, VA or Hillsboro, OR (US)
- Singapore
For EU data residency, choose one of the European locations.
-
Choose the image
Under Image, make sure OS Images is selected and pick Ubuntu (the latest LTS version).
-
Add your SSH key (required)
Scroll to the SSH keys section and attach an SSH key. This is required — without it you can’t upgrade Pinchy later, hand out production secrets, or enable HTTPS. Hetzner never sets a root password on Ubuntu cloud images, so the SSH key is your only way in.
I don’t have an SSH key yet
An SSH key is a pair of files that proves you are you when connecting to a server. It replaces passwords and is far more secure. Creating one takes under a minute.
On your computer, open a terminal:
- Mac — open Terminal (⌘+Space, type “Terminal”)
- Windows — open PowerShell (Start menu)
- Linux — any terminal app
Check if you already have a key:
Terminal window cat ~/.ssh/id_ed25519.pubIf you see a line starting with
ssh-ed25519, you already have a key — copy the output and skip to the next step. If you get “No such file or directory”, create one:Terminal window ssh-keygen -t ed25519 -C "your-email@example.com"Press Enter three times to accept defaults (empty passphrase is fine for a throwaway demo; use one for production). Then display and copy the public key:
Terminal window cat ~/.ssh/id_ed25519.pubAdd it to Hetzner:
Back in the Cloud Console, the SSH keys section has an Add SSH Key button. Paste the full public key (the
ssh-ed25519 AAAA…line, including your email), give it a name, and save. It now appears in the list — tick its checkbox to attach it to this server. -
Paste the setup script
Scroll down past Volumes, Firewalls, Backups, Placement groups, and Labels until you reach Cloud config — it’s near the very bottom. Paste the following script into the “Cloud-init configuration” text area:
cloud-init.yml #cloud-config# Pinchy v0.5.4 — Automated VPS Setup# https://docs.heypinchy.com/guides/vps-deployment/## This cloud-init script installs Caddy (reverse proxy), Docker, and Pinchy,# configures a firewall, and adds swap. Paste it into the "User Data" field# when creating your server.## After boot, visit http://<your-server-ip>. Caddy shows a loading page while# images are pulling, then seamlessly hands off to Pinchy — no refresh needed.# To enable HTTPS later, replace `:80` in /etc/caddy/Caddyfile with your domain# and run `systemctl reload caddy` — Let's Encrypt is automatic.runcmd:# Add Caddy's official Cloudsmith apt repository (GPG-verified)- apt-get update -qq- apt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl gpg- curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg- curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list- apt-get update -qq# Stage loading page for Caddy's fallback upstream- mkdir -p /var/www/pinchy-loading- curl -fsSL https://github.com/heypinchy/pinchy/releases/download/v0.5.4/installing.html -o /var/www/pinchy-loading/index.html- sed -i "s/INSTALL_START_TIME/$(date +%s)000/" /var/www/pinchy-loading/index.html# Write Caddyfile BEFORE installing caddy, so Caddy's first start uses our config- mkdir -p /etc/caddy- |cat > /etc/caddy/Caddyfile <<'EOF'# Pinchy reverse proxy# Primary upstream: Pinchy on 127.0.0.1:7777# Fallback upstream: local loading page on 127.0.0.1:9999## lb_policy first → prefer primary; fall back only when primary is unreachable.# lb_try_duration 1s → fail fast so users never wait for a dead backend.# fail_duration 5s → mark unreachable backend as dead for 5s before retry.## To enable HTTPS: replace `:80` with your domain (e.g. pinchy.example.com)# and run `systemctl reload caddy`. Caddy provisions Let's Encrypt automatically.:80 {reverse_proxy 127.0.0.1:7777 127.0.0.1:9999 {lb_policy firstlb_try_duration 1sfail_duration 5s}}:9999 {bind 127.0.0.1handle /api/* {respond 503}handle {root * /var/www/pinchy-loadingtry_files /index.htmlfile_server}}EOF# Install Caddy, Docker, and UFW in one apt call (Caddy starts with our Caddyfile).# --force-confold keeps our pre-staged /etc/caddy/Caddyfile without dpkg's# interactive conffile prompt, which would otherwise fail cloud-init (no stdin)# and leave the caddy package in iU state (postinst skipped, `caddy` user never# created, systemd start fails with exit 217/USER).- DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::=--force-confold caddy docker.io docker-compose-v2 ufw- systemctl enable --now caddy- systemctl reload caddy# Firewall- ufw allow OpenSSH- ufw allow 80/tcp- ufw allow 443/tcp- ufw --force enable# Docker- systemctl enable docker- systemctl start docker# Swap (recommended for 4 GB servers)- fallocate -l 2G /swapfile- chmod 600 /swapfile- mkswap /swapfile- swapon /swapfile- echo '/swapfile none swap sw 0 0' >> /etc/fstab# Generate pinned secrets — Pinchy keeps its secure 127.0.0.1:7777 default,# reachable only via Caddy on the same host.- mkdir -p /opt/pinchy- echo "PINCHY_VERSION=v0.5.4" >> /opt/pinchy/.env- echo "DB_PASSWORD=$(openssl rand -hex 32)" >> /opt/pinchy/.env- echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" >> /opt/pinchy/.env- echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> /opt/pinchy/.env- chmod 600 /opt/pinchy/.env# Download compose file and pull images (Caddy serves loading page throughout)- curl -fsSL https://raw.githubusercontent.com/heypinchy/pinchy/v0.5.4/docker-compose.yml -o /opt/pinchy/docker-compose.yml- cd /opt/pinchy && docker compose pull# Start Pinchy — Caddy automatically swaps to the primary upstream within 5s- cd /opt/pinchy && docker compose up -d# Wait for Pinchy to be healthy (polled directly, not through Caddy)- for i in $(seq 1 120); do curl -sf http://127.0.0.1:7777/api/health > /dev/null 2>&1 && break; sleep 2; doneWhat does this script do?
This is a cloud-init script — a standard way to configure servers on first boot. When your server starts, it automatically:
- Installs Caddy, Docker, and a firewall
- Opens only the ports needed (SSH, HTTP, HTTPS)
- Adds 2 GB of swap space (a safety net for heavy agent workloads)
- Configures Caddy as a reverse proxy with a built-in loading page — so visitors see a friendly page during install and through any future Pinchy restart
- Downloads and starts Pinchy from pre-built Docker images (bound to
127.0.0.1:7777, reachable only via Caddy)
No manual setup required. You can also download the script separately.
-
Give your server a name and create it
Scroll down to Name, enter
pinchy(or whatever you prefer), and click Create & Buy now. Your server will be ready in about 20 seconds, but the setup script needs another about a minute to pull the Pinchy images. -
Note the IP address
After creation, you’ll see the server’s detail page. The IPv4 address is shown at the top (e.g.,
116.202.x.x). You’ll need this to access Pinchy. -
Open Pinchy
Visit
http://<your-server-ip>in your browser (replace<your-server-ip>with the actual IP address). You’ll see a loading page while Pinchy finishes installing, then it automatically hands off to the setup wizard — no refresh needed.The setup wizard will guide you through creating your admin account and configuring your first LLM provider.
Production setup
Section titled “Production setup”The steps above get Pinchy running over plain HTTP on port 80. Before using it with real data, add HTTPS and lock your domain.
All commands below run on your server. You have two options to connect:
- Web console — On the server’s detail page in Hetzner, click the >_ icon (top-right) to open a browser-based terminal. No SSH key needed.
- SSH — Connect from your local terminal with
ssh root@<your-server-ip>.
How do I set up SSH access?
SSH lets you run commands on your server from your local terminal. It’s faster and more comfortable than the web console.
1. Open a terminal on your computer:
- Mac — Open Terminal (search in Spotlight with ⌘+Space)
- Windows — Open PowerShell (search in Start menu) or install Windows Terminal
- Linux — Open your terminal app (usually Ctrl+Alt+T)
2. Check if you already have an SSH key:
cat ~/.ssh/id_ed25519.pubIf you see a line starting with ssh-ed25519, skip to step 4. If you get “No such file”, continue with step 3.
3. Create an SSH key:
ssh-keygen -t ed25519 -C "your-email@example.com"Press Enter to accept the default file location, then choose a passphrase (or press Enter twice for no passphrase).
4. Add the key to Hetzner:
Display your public key and copy the output:
cat ~/.ssh/id_ed25519.pubIn the Hetzner Console, go to Security in the left sidebar → SSH Keys → Add SSH key. Paste your key and give it a name.
5. Rebuild your server with the SSH key (or create a new one with the key selected). Then connect:
ssh root@<your-server-ip>While connected, every command you type runs on your server. Type exit to disconnect.
Set up HTTPS with Caddy
Section titled “Set up HTTPS with Caddy”Caddy is already installed and serving Pinchy on port 80 — the cloud-init script set it up. Switching to HTTPS is a single edit.
Open the Caddy configuration:
nano /etc/caddy/CaddyfileReplace the :80 { line at the top with your domain (keep the rest of the file as is):
pinchy.example.com { reverse_proxy 127.0.0.1:7777 127.0.0.1:9999 { lb_policy first lb_try_duration 1s fail_duration 5s }}Save the file (Ctrl+O, Enter, Ctrl+X) and reload Caddy:
systemctl reload caddyTell Pinchy its public HTTPS URL so auth callbacks and redirects are generated with your real domain. Use the same domain you put in the Caddyfile, including https:// and no trailing slash:
cd /opt/pinchygrep -q '^BETTER_AUTH_URL=' .env \ && sed -i 's|^BETTER_AUTH_URL=.*|BETTER_AUTH_URL=https://pinchy.example.com|' .env \ || echo 'BETTER_AUTH_URL=https://pinchy.example.com' >> .envdocker compose up -d pinchyAfter a few seconds, visit https://pinchy.example.com — Caddy automatically provisions a Let’s Encrypt certificate. The loading-page fallback on :9999 stays unchanged so users still see a friendly page if Pinchy ever becomes unreachable during an upgrade or restart.
Set the public Better Auth callback origin to the same HTTPS domain:
cd /opt/pinchyprintf '\nBETTER_AUTH_URL=https://pinchy.example.com\n' >> .envdocker compose up -dBETTER_AUTH_URL is separate from Domain Lock. It is used for absolute callback and redirect URLs such as password reset, OAuth, and Telegram pairing.
Lock your domain
Section titled “Lock your domain”Once HTTPS is working, open Settings → Security in the Pinchy web UI and enter your domain (e.g. pinchy.example.com).
After saving, Pinchy will reject requests from any other origin with a 403 error — including access via the raw server IP address. The domain lock should match the hostname in BETTER_AUTH_URL; together they keep security checks and generated callback URLs on the same public origin.
Point your domain (optional but recommended)
Section titled “Point your domain (optional but recommended)”To use HTTPS, you need a domain pointing to your server. Add a DNS record at your domain registrar (the service where you registered your domain):
| Type | Name | Value |
|---|---|---|
| A | pinchy (or your preferred subdomain) | Your server’s IP address |
What’s an A record?
An A record is a DNS entry that maps a domain name (like pinchy.example.com) to an IP address (like 116.202.1.23). When someone visits your domain, their browser looks up the A record to find your server.
Look for “DNS records” or “DNS settings” in your domain registrar’s dashboard.
Hetzner-specific tips
Section titled “Hetzner-specific tips”Snapshots for backups
Section titled “Snapshots for backups”Hetzner offers server snapshots (€0.012/GB/month). Take a snapshot after your initial setup — it lets you restore the entire server in minutes if something goes wrong.
Servers → your server → Snapshots → Create Snapshot
Cloud firewall
Section titled “Cloud firewall”Hetzner offers a Firewall feature under Security → Firewalls. For defense in depth, configure the cloud firewall to only allow ports 22, 80, and 443 — in addition to the host firewall (ufw) that the setup script already configured.
Volumes for persistent data
Section titled “Volumes for persistent data”For larger Knowledge Base deployments, you can attach a Hetzner Volume (up to 10 TB) and mount it as a data directory. See Mount Data Directories for details.
Scaling up
Section titled “Scaling up”If you outgrow your server, you can resize to a larger type directly in the Hetzner console. Pinchy’s data lives in Docker volumes, so it survives server resizes. Plan for a brief downtime window (a few minutes) during the resize.