Skip to main content
Run Grind on any Linux VPS or server for 24/7 availability and always-on webhook delivery. Both the web app and gateway run as local processes — a reverse proxy handles TLS and external traffic.
Private by default. The web app has no built-in authentication layer. Keep it private via SSH tunnel or Tailscale unless you add an external auth gate (Cloudflare Access, HTTP basic auth, VPN). Only the gateway’s /hooks/* endpoints need to be publicly reachable — and only if you use Telegram, Discord, or WhatsApp integrations.

VPS requirements

Any modern Linux distribution works. Ubuntu LTS is the most tested path.

Architecture

Both services bind to 127.0.0.1 by default. Only the reverse proxy is publicly reachable.

Quickstart

1

Install Bun + Grind

2

Initialize your vault

Creates ~/.grind/, generates an encryption key, and sets up your profile. Back up the key immediately — see Step 2 below.
3

Start services

4

Verify locally

For a production setup with autostart and TLS, follow the full steps below.

Step 1: Install Grind

See Installation for all options. On a Linux server:
Verify:

Step 2: Initialize

The setup wizard runs interactively. On a headless server, run it in any terminal (SSH session is fine). Back up your encryption key immediately. Without it the vault is unrecoverable:
Store the printed key in a password manager or secrets store.

Step 3: Configure environment

Set production values in an env file. Grind reads process.env, so any mechanism works (systemd EnvironmentFile, Docker env_file, shell export). /etc/grind.env (or ~/.grind/.env):
Generate a secure gateway token:
See Environment Reference for the full variable list.

Step 4: Keep services running (systemd)

Enable user lingering

Grind’s gateway start installs a systemd user unit automatically. On a headless VPS, enable linger so user services survive logout and start on boot:
Then start the gateway (autostart unit is installed automatically):
Verify:

Web app unit

The web app has no built-in autostart. Create a user service:
Check:

Alternative: system-level units

If you prefer system-level services (no linger required):
If you use system-level units, run grindxp gateway disable first to remove the user-level unit that gateway start installed — otherwise both will compete to run.

Alternative: PM2


Step 5: Reverse proxy + TLS

The reverse proxy terminates TLS and routes traffic to local services. Recommended routing:
  • hooks.example.com/hooks/*http://127.0.0.1:5174 — webhook endpoints (public, signature-verified by each integration)
  • app.example.comhttp://127.0.0.1:3000 — web app (add an auth gate before exposing publicly)
The web app has no built-in authentication. Do not expose it on a public hostname without an auth gate. Use Cloudflare Access, HTTP basic auth, or a VPN/Tailscale tunnel for personal access instead.
Caddy handles TLS automatically via Let’s Encrypt — no certbot needed.
For other distros see the official Caddy install docs./etc/caddy/Caddyfile:

Step 6: Firewall

Allow only SSH and the reverse proxy. Everything else stays closed.
The web app (:3000) and gateway (:5174) are not opened — only the reverse proxy on the same host can reach them via localhost.

Step 7: Verify


Webhooks

If you use Telegram, Discord, or WhatsApp integrations, the gateway needs a public HTTPS URL. The reverse proxy above handles TLS termination. Register these webhook URLs with each provider: Run grindxp integrations to connect credentials. Set the corresponding verification secrets in your env file — requests without valid signatures are rejected:
See Environment Reference and Integrations for details.

Docker

Initialize Grind on your local machine first (grindxp init), then copy ~/.grind/ to the server before running containers. docker-compose.yml:
.env:
Copy your initialized vault into the named volume:
Start:
For production, build a custom image with grindxp pre-installed so startup is fast and reproducible. Install it in the Dockerfile with RUN bun install -g grindxp rather than at container startup.

Platform examples

The CX22 (2 vCPU, 4 GB RAM, ~€4/month) is the recommended starting point. CX11 (2 GB) works for personal use.
  1. Open the Hetzner Cloud Console and create a server. Choose any supported Linux distro (Ubuntu 24.04 recommended) and add your SSH key during provisioning.
  2. SSH in as root and create a non-root user:
  3. Follow the Quickstart then the full steps above.
  4. Point DNS A records at the VPS IP:
    • hooks.example.com → <vps-ip> (public, for webhooks)
    • app.example.com → <vps-ip> (optional — keep private unless you add auth)
  5. Use the Caddy config for automatic TLS with zero extra configuration.
  6. In the Cloud Console, add a Cloud Firewall that allows TCP 22, 80, 443 inbound — all other ports blocked.
A Basic Droplet ($6/month, 1 vCPU, 1 GB RAM) covers personal Grind usage.
  1. Create a Droplet (Ubuntu 24.04 recommended; any supported Linux distro works). In Networking → Firewalls, allow TCP 22, 80, 443 inbound.
  2. SSH in and follow the Quickstart.
  3. Set DNS A records in Networking → Domains.
  4. Use Caddy or Nginx — both work well.
DigitalOcean’s managed firewall and a host-level firewall (UFW, firewalld) are independent layers. Configure one or the other — not both — to avoid hard-to-debug conflicts. The managed firewall is easier to audit from the cloud console.
The Oracle Cloud Free Tier includes Ampere A1 ARM compute (up to 4 OCPUs + 24 GB RAM total) and 200 GB block storage — permanently free, no expiry.
  1. Sign up and create a Compute instance. Choose Ubuntu 22.04 or later on Ampere A1 (ARM / aarch64). Grind and Bun both have full arm64 support.
  2. Open ports 80 and 443 in the OCI Security List (Networking → Virtual Cloud Networks → your VCN → Security Lists). The OS firewall alone is not sufficient on OCI — the Security List sits at the hypervisor level and must also allow the ports.
  3. Follow the Quickstart.
  4. Use Caddy — it’s the easiest path on ARM.
Fly.io’s shared-cpu-1x with 512 MB RAM works for Grind. See the fly.toml reference for all config options.fly.toml:
Create a persistent volume for ~/.grind/:
Fly handles TLS automatically. The web app is not deployed here by default — expose it separately or access via SSH tunnel with fly ssh console.
The guide above works on any Linux VPS. Key points:
  • OS: Any modern Linux distro with systemd (Ubuntu 20.04+, Debian 11+, Fedora 38+, RHEL 9+, Arch).
  • Bun: curl -fsSL https://bun.com/install | bash — see Bun install docs for other methods.
  • Grind: bun install -g grindxp
  • Autostart: loginctl enable-linger $USER then grindxp gateway start, plus a user systemd unit for the web app.
  • Firewall: use your provider’s managed firewall panel if one exists — don’t run both it and a host-level firewall.
  • TLS: Caddy is the simplest option on any provider — it auto-provisions Let’s Encrypt certificates.

Backup

Back up these two files regularly. Without config.json the vault is permanently unrecoverable: Simple daily cron (adjust paths for your backup target):
Or with rclone to S3-compatible storage:
See Security for the full key management guide.

Troubleshooting

First 60 seconds if something is broken

systemctl --user fails: “Failed to connect to bus”

On a headless VPS without a login session, the D-Bus session bus is unavailable. Fix:

Gateway port already in use

Reverse proxy returns 502

The service is not running or is bound to the wrong address:
Both services must be bound to 127.0.0.1 (or 0.0.0.0) and running before the proxy can reach them.

Webhook 401 / signature errors

The verification secret doesn’t match what was registered with the provider. Re-run grindxp integrations to update credentials, and ensure the matching env var is set:
Restart the gateway after changing env vars.

Encryption key missing after reinstall

The key is in ~/.grind/config.json. If you deleted it without backing it up, the vault is unrecoverable. This is why backing up config.json before anything else is critical.