# Self-host on a VPS

Checked against the code on 2026-10-30 (version 0.1.0).

Today SeeDropHost runs on your own server: there is no public hosted instance yet. This page summarises the full step-by-step guide (in Portuguese) that ships with the code as `docs/VPS.md`. It takes about 30 minutes and is copy-and-paste.

## What you need

- A VPS with **Ubuntu 24.04** and **2 GB of memory**. A data centre close to your visitors helps.
- A domain, for example `mysites.example`.
- Optional but recommended: a free Cloudflare account in front.

## 1. DNS (with Cloudflare in front)

| Type | Name | Content | Proxy |
|---|---|---|---|
| A | `@` | your server IP | proxied |
| A | `*` (every site) | your server IP | proxied |
| A | `edge` | your server IP | **DNS only** |

`edge` is where your customers' domains point (CNAME); it stays "DNS only" so domains in other Cloudflare accounts can point to it. Leave **Always Use HTTPS off** — the server redirects to HTTPS itself, and the Let's Encrypt HTTP challenge for customer domains must reach port 80.

**Wildcard origin certificate.** In Cloudflare, create an origin certificate for `mysites.example` and `*.mysites.example`. After step 2, paste it into `/etc/seedrophost/tls/origin.pem` and `origin.key` and run the installer again. Use SSL/TLS mode **Full** until it is installed, then **Full (strict)**.

## 2. Install

```bash
ssh root@YOUR-SERVER-IP
apt-get update && apt-get install -y git
git clone <repository-url> /opt/seedrophost
sudo bash /opt/seedrophost/deploy/install.sh mysites.example you@example.com
```

The installer:

- installs Node.js 24 (official package, SHA256 checked) and Caddy;
- creates the `seedrophost` user, the data folder `/var/lib/seedrophost`, the settings file `/etc/seedrophost/env`, and a random **token** and **master key** in `/etc/seedrophost/credentials/` (readable by root only, never in the repository), handed to the service as systemd credentials;
- starts a hardened systemd service (`NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome` and more), the daily backup and a firewall (ports 22, 80, 443).

Running it again is safe: existing secrets are kept. Read your token with `sudo cat /etc/seedrophost/credentials/sdh_token`.

> **Warning:** store the master key (`/etc/seedrophost/credentials/sdh_master_key`) in a password manager. Without it, secret site variables cannot be read back, not even from a restored backup.

## 3. HTTPS

Caddy sits in front. The base domain, every `<site>.<base>`, every deploy preview and the drop domain share the **wildcard certificate** from step 1, so no certificate is requested per site. Customer domains get a Let's Encrypt certificate on their first visit (on-demand TLS), but only when Caddy asks SeeDropHost and the domain is **already verified by TXT**; the dashboard host gets one the same way when it is outside the base. Anything else gets no certificate, so nobody can exhaust your certificate quota by pointing random names at the server. SeeDropHost itself listens only on `127.0.0.1`.

## Dashboard, sites and anonymous drops

- The installer sets `SDH_PANEL_HOST` to the base domain: the dashboard, API and MCP answer only there, and no site is ever served on that host. Sites live at `<site>.<base>`.
- Publishing without an account (`SDH_ANON_DROP=1`) is off by default. If you turn it on, give it a separate domain with `SDH_DROP_DOMAIN` (same `@` and `*` records, and its own origin certificate in `drop-origin.pem` / `drop-origin.key`), so a phishing page published anonymously never carries your main domain. [Drop limits](/docs/limits/).
- Optional: listing the drop domain (or the base) in the Public Suffix List makes browsers treat each site as a separate site. It takes weeks.

## 4. First deploy

Open `https://mysites.example`, sign in to the dashboard with the token and drop a folder, or from your computer:

```bash
export SDH_API_URL=https://mysites.example
export SDH_TOKEN=paste-the-token
npx sdh create my-site
npx sdh deploy ./site-folder --site my-site
```

The site is at `https://my-site.mysites.example`.

## 5. Remote MCP

```bash
claude mcp add --transport http seedrophost https://mysites.example/mcp --header "Authorization: Bearer YOUR-TOKEN"
```

Everything Claude does is in the audit log as `mcp-remoto`. The remote MCP does not read files on the server. [MCP details](/docs/mcp/).

## 6. Backup, restore, update, take down

- **Backup:** every day at 03:30 the server copies the database and site files to `/var/backups/seedrophost/seedrop-YYYYMMDD-HHMMSS` and deletes copies older than 14 days. Run one now with `sudo bash /opt/seedrophost/deploy/backup.sh`. Keep a copy off the VPS (provider snapshots or `scp`).
- **Restore:** stop the service, copy the database and the `blobs/` folder back, fix ownership, start the service (exact commands in `docs/VPS.md`). On a new server, put the same master key and token in `/etc/seedrophost/credentials/`.
- **Update:** `sudo bash /opt/seedrophost/deploy/update.sh` backs up, fetches the new version, installs, runs the tests as the `seedrophost` user (never root) and restarts only if they pass.
- **Take a site down at once** (phishing, abuse): `DELETE /v1/sites/{site}` with the token. It removes the site with its deploys, domains, forms and leads, and is recorded in the audit log.

## 7. Configuration

| Variable | What it is |
|---|---|
| `SDH_TOKEN` | API token (required outside dev mode; from systemd credentials on the VPS) |
| `SDH_MASTER_KEY` | encryption key for site variables (required outside dev mode; from systemd credentials on the VPS) |
| `SDH_BASE_DOMAIN` | sites live at `<site>.<base>` |
| `SDH_PANEL_HOST` | the only host for dashboard, API and MCP; never serves a site |
| `SDH_PORT` / `SDH_HOST` | port and listen address (`127.0.0.1` behind Caddy) |
| `SDH_EDGE_CNAME` / `SDH_EDGE_IPV4` | what customers point their DNS to |
| `SDH_DATA_DIR` | database and files |
| `SDH_ANON_DROP=1` | allows publishing without a token (off by default) |
| `SDH_DROP_DOMAIN` | separate domain for anonymous drops |
| `SDH_ANON_DROP_MAX_HOUR` / `SDH_ANON_DROP_MAX_MB` | server-wide drops per hour (default 60) and total drop storage in MiB (default 2048) |
| `SDH_FUNCTIONS=1` | runs functions — only if you own every site ([why](/security/#functions)) |
| `SDH_TRUST_PROXY=1` | read the visitor IP from `X-Forwarded-For` (only behind your own proxy) |
| `SDH_ACCESS_LOG=1` | one JSON line per request, with a per-process IP alias instead of the raw IP |

Health check: `curl https://mysites.example/v1/health`.

## Known risks

| Risk | What to do |
|---|---|
| A phishing page on an anonymous drop stays up to 24 hours | delete it with `DELETE /v1/sites/{site}`; use `SDH_DROP_DOMAIN` |
| `x-sdh-actor` in the audit log is declared by the client | treat it as a label; the token is the identity |
| `Country=` rules trust `CF-IPCountry` | without Cloudflare in front, visitors can fake it |
| Rate counters live in memory | a restart resets them |
| Functions are not isolated | turn them on only if you own every site |

## Costs

Your server and domain. The guide estimates about R$ 35–80 per month (October 2026 prices, Brazil) for a 2 GB VPS, provider backup and a `.com.br` domain, with Cloudflare's free plan and free Let's Encrypt certificates. Hosted SeeDropHost plans: see [pricing](/pricing/).
