SpipCP

First-run without a domain

The terminal-to-web bootstrap when you have only a VPS and an IP — no DNS, no Cloudflare. Reach the panel by IP first, become your own DNS provider from inside the panel, then cut over to a real hostname with automatic TLS.

The standard install assumes you already have a domain pointed at the box. This page is for the other case: you have a VPS (an IP, plus SSH) and a domain at a registrar, but no DNS provider you want to keep — no Cloudflare anywhere. By the end the panel runs at https://panel.example.com/ with a real certificate, and example.com is answered by your own PowerDNS nameservers, managed from inside the panel.

There is a deliberate ordering here because of one circular fact: you want panel.example.com to be served by your PowerDNS, but PowerDNS doesn't exist until you set it up in the panel, and you can't open the panel by hostname until DNS resolves. The way out is to reach the panel by IP first, build DNS from there, and switch to the hostname last. That's the whole arc:

1. Install on the VPS, reach the panel by IP        (no DNS at all)
2. Create the admin account                         (over https://<ip>/)
3. Enroll a PowerDNS box as a nameserver node       (in the panel UI)
4. Create the example.com zone + records            (in the panel UI)
5. Set glue + NS for example.com                    (at the registrar — the one unavoidable step)
6. Cut the panel over to panel.example.com + real TLS (edit .env, restart)
7. Enroll the rest of the fleet (site/app nodes)     (only now, on the hostname)

You can pause after step 2 for as long as you like — the panel is fully usable over its IP. Steps 3–6 are the "become your own DNS provider" part, and the Self-hosted DNS section covers each in depth.

Before you start

  • A Linux VPS (Ubuntu/Debian) with root/sudo, its public IP, and SSH (key or password).
  • The image must be reachable: the public default ghcr.io/spipov/spipcp, or your own registry.
  • Your domain at a registrar whose control panel lets you (a) edit DNS records for the zone and (b) set the nameservers + glue for the domain. (Nearly all do — that's all you need from them; you are not keeping DNS there long-term.)
  • A second small VPS for your first nameserver (ns1). A third for ns2 is strongly recommended — most registrars require at least two nameservers to delegate.

Generate the secrets once (keep them safe — they're permanent)

On your laptop or the box:

openssl rand -hex 32   # BETTER_AUTH_SECRET
openssl rand -hex 32   # AGENT_GATEWAY_SECRET
openssl rand -hex 32   # MASTER_KEY        (field-encryption key — never rotate casually)
openssl rand -hex 24   # POSTGRES_PASSWORD

MASTER_KEY is permanent

MASTER_KEY encrypts every secret the panel stores — SSH creds, PowerDNS API keys, certs. If you lose it you lose access to those secrets. Store it in a password manager and in your recovery kit, generated in the setup wizard.

Install on the VPS, reachable by IP (no DNS yet)

SSH in and run the installer. The key choice at this stage: SITE_ADDRESS = the VPS IP and TLS_MODE=internal, so Caddy serves a self-signed cert and the panel answers on the raw IP with no DNS anywhere.

ssh root@<vps-ip>

curl -fsSL https://<your-git-host>/SpipCP/spipcp/raw/branch/main/install.sh -o install.sh
chmod +x install.sh

SITE_ADDRESS=<vps-ip> \
TLS_MODE=internal \
BETTER_AUTH_SECRET=<paste> \
AGENT_GATEWAY_SECRET=<paste> \
MASTER_KEY=<paste> \
POSTGRES_PASSWORD=<paste> \
SPIPCP_TAG=latest \
./install.sh --repo https://<your-git-host>/SpipCP/spipcp.git --yes

The installer provisions Docker if missing, validates the .env (it refuses to start half-configured), pulls the image, and brings the stack up. When it finishes:

docker compose ps                       # everything healthy
curl -fsSk https://<vps-ip>/healthz     # -k because the cert is self-signed

Why IP and not the hostname yet?

Nothing authoritative for your domain is publishing the panel hostname yet, and a real Let's Encrypt cert needs the hostname to resolve. Internal TLS on the IP sidesteps both so you can get in immediately.

Open the web UI and create the admin

Browse to https://<vps-ip>/. Your browser will warn that the certificate isn't trusted (it's Caddy's internal CA) — accept it once and continue. The first account you create becomes the admin, and it's pre-verified, so you're in even before email is configured. Set up 2FA while you're here.

The setup wizard reached by IP
📷The setup wizard — reachable over https://<vps-ip>/ before any DNS exists. Create the admin, verify MASTER_KEY, and generate the recovery kit.img/setup-recovery-kit.avif
The setup wizard — reachable over https://<vps-ip>/ before any DNS exists. Create the admin, verify MASTER_KEY, and generate the recovery kit.

That's the terminal-to-web handoff: from here on everything is done in the UI. This is also the right moment to generate your recovery kit.

Setup wizard

Stand up your first nameserver (PowerDNS) as a node

DNS is managed under the Networking section (Domains · Providers · Nameservers · Edge proxy · SSL).

  1. Networking → Domains (empty until you attach a hostname) and pick the Self-hosted nameservers posture — maximum data ownership, you hold every record.
  2. Networking → Nameservers → Add nameserver — ONE wizard does everything: enter ns1's host/IP, SSH port, user, and paste the SSH key or password your provider gave you. The panel SSHes in once, installs and hardens the agent, then installs PowerDNS and generates the PowerDNS API key itself — you never type or see it; it's encrypted at rest. The box also appears under Nodes afterwards (a nameserver IS a node; the pages cross-link). From then on it never needs SSH again — the agent dials home over its own authenticated WebSocket.
  3. Repeat for ns2 on the second box (you'll want two for delegation).

The agent binary the wizard installs is baked into the panel image and auto-published, signed — there is no "publish a release" step; the first enrollment just works. Make sure the boxes' provider firewall (if any) allows inbound 53/udp + 53/tcp — they're about to be public authoritative nameservers.

Adding the nameserver node by IP
📷Nodes → Add node — give SSH access once; the panel installs and hardens the agent, then never SSHes the box again.img/nodes-add.avif
Nodes → Add node — give SSH access once; the panel installs and hardens the agent, then never SSHes the box again.

Nodes · Self-hosted DNS

Create the zone and its records

In Networking → Nameservers, create the zone for your domain (served by your PowerDNS boxes), then add:

RecordTypeValueWhy
panel.example.comA<panel vps-ip>the panel's permanent hostname
ns1.example.comA<ns1 ip>your first nameserver
ns2.example.comA<ns2 ip>your second nameserver
example.com (apex)Aas you likethe bare domain, optional

Replace example.com with your actual domain throughout. The apex SOA/NS are panel-managed and locked so you can't accidentally break delegation. Wildcards (*.example.com) are supported for customer sites later.

The Networking Nameservers page
📷Networking → Nameservers — create the zone and its records on your own PowerDNS boxes.img/dns-servers-golive-order.avif
Networking → Nameservers — create the zone and its records on your own PowerDNS boxes.

At this point your PowerDNS boxes can answer for your domain — but the world still asks your registrar, because the registrar hasn't been told to point at your boxes yet. That's the next, and only, registrar step.

Delegate a domain

Point the domain at your nameservers (at the registrar)

This is the one thing the panel cannot do for you, by design: the registrar always owns the domain's delegation. In the registrar control panel for your domain:

  1. Add glue records (a.k.a. "register host" / "private nameservers"): tell the registry that ns1.example.com = <ns1 ip> and ns2.example.com = <ns2 ip>. Glue is required because the nameservers live inside the domain they serve — without it resolvers can't find them.
  2. Set the nameservers for your domain to ns1.example.com and ns2.example.com, replacing whatever is there now. This is the "take over from Cloudflare" moment.

Delegation propagates over minutes to a couple of hours. Verify:

dig +trace NS example.com                # should end at ns1/ns2.example.com
dig @ns1.example.com panel.example.com A # your box answers with the panel IP

Because you recreated the panel hostname (and everything else) in your PowerDNS in the previous step, the cutover is seamless — both sides serve the same records.

No lock-out risk

The panel hostname being served by the very PowerDNS the panel manages is safe. PowerDNS answers queries on its own; the panel only writes records into it. If the panel is ever down, DNS keeps resolving.

Cut the panel over to the hostname + real TLS

Now that panel.example.com resolves to the panel box, switch the panel off the IP/self-signed cert and onto its real hostname with automatic Let's Encrypt. Edit /opt/spipcp/.env on the panel VPS:

.env
SITE_ADDRESS=panel.example.com               # permanent — agents pin this; don't change it again
TLS_MODE=you@example.com                     # real email → real Let's Encrypt cert
WORKER_WSS_URL=wss://panel.example.com/ws    # the URL enrolled agents dial home on
AGENT_BINARY_URL=https://panel.example.com   # where nodes fetch the agent binary
BETTER_AUTH_URL=https://panel.example.com    # auth origin must match the public host

Restart the stack:

cd /opt/spipcp
docker compose up -d
curl -fsS https://panel.example.com/healthz   # no -k now — the cert is real

The panel is live at https://panel.example.com/. Caddy obtained the cert automatically over ACME HTTP-01 the moment ports 80/443 were reachable on the resolving hostname.

Why SITE_ADDRESS is permanent

Every node agent pins the hostname it dials. That's why you enrolled only the nameserver node before this cutover — you needed it to create the URL, and its agent reaches the worker by the panel box's stable address. Enroll the rest of the fleet after this step so they pin your panel hostname from the start.

From here

  • Add the rest of the fleetNodes → Add node for each app/site box, then deploy sites with domains, SSL, databases, and backups.
  • Issue customer domains — with your own nameservers authoritative you can now write any record and issue certs (including wildcards via DNS-01) for any site, straight from the panel. → Domains & SSL
  • Arm recovery — generate the recovery kit so the boot secrets exist out-of-band before any backup is taken. → Disaster recovery
  • Day-2 ops & upgrades — pin SPIPCP_TAG to a release (not latest) and update on your schedule. → Updating the panel

Quick reference: the env vars that change between stages

VariableStage 1 (IP)Stage 6 (hostname)
SITE_ADDRESS<vps-ip>panel.example.com (permanent)
TLS_MODEinternalyou@example.com
WORKER_WSS_URLunsetwss://panel.example.com/ws
AGENT_BINARY_URLunsethttps://panel.example.com
BETTER_AUTH_URLhttps://<vps-ip>https://panel.example.com
BETTER_AUTH_SECRET / AGENT_GATEWAY_SECRET / MASTER_KEY / POSTGRES_PASSWORDset once, stable forever(unchanged)

See the full .env reference for every variable.

On this page