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 forns2is 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_PASSWORDMASTER_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 --yesThe 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-signedWhy 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.
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.
Stand up your first nameserver (PowerDNS) as a node
DNS is managed under the Networking section (Domains · Providers · Nameservers · Edge proxy · SSL).
- Networking → Domains (empty until you attach a hostname) and pick the Self-hosted nameservers posture — maximum data ownership, you hold every record.
- 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. - Repeat for
ns2on 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.
→ 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:
| Record | Type | Value | Why |
|---|---|---|---|
panel.example.com | A | <panel vps-ip> | the panel's permanent hostname |
ns1.example.com | A | <ns1 ip> | your first nameserver |
ns2.example.com | A | <ns2 ip> | your second nameserver |
example.com (apex) | A | as you like | the 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.
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.
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:
- Add glue records (a.k.a. "register host" / "private nameservers"): tell the registry that
ns1.example.com=<ns1 ip>andns2.example.com=<ns2 ip>. Glue is required because the nameservers live inside the domain they serve — without it resolvers can't find them. - Set the nameservers for your domain to
ns1.example.comandns2.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 IPBecause 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:
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 hostRestart the stack:
cd /opt/spipcp
docker compose up -d
curl -fsS https://panel.example.com/healthz # no -k now — the cert is realThe 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 fleet — Nodes → 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_TAGto a release (notlatest) and update on your schedule. → Updating the panel
Quick reference: the env vars that change between stages
| Variable | Stage 1 (IP) | Stage 6 (hostname) |
|---|---|---|
SITE_ADDRESS | <vps-ip> | panel.example.com (permanent) |
TLS_MODE | internal | you@example.com |
WORKER_WSS_URL | unset | wss://panel.example.com/ws |
AGENT_BINARY_URL | unset | https://panel.example.com |
BETTER_AUTH_URL | https://<vps-ip> | https://panel.example.com |
BETTER_AUTH_SECRET / AGENT_GATEWAY_SECRET / MASTER_KEY / POSTGRES_PASSWORD | set once, stable forever | (unchanged) |
See the full .env reference for every variable.
Related
- Installation — the standard install when you already have a domain.
- DNS: the two layers — delegation vs. records, and the posture chooser.
- Self-hosted DNS — running your own PowerDNS nameservers in depth.
- Setup wizard — admin account,
MASTER_KEY, recovery kit, SMTP.


