CoreCP

Adding a server

From a bare Ubuntu server to a working role node in one command. You say in the panel what the machine is for, copy one line, paste it on the server, and watch.

Written for: Administrator

From a bare Ubuntu server to a working role node in one command. You say in the panel what the machine is for, copy one line, paste it on the server, and watch.

Administrators only. Resellers and end customers never see which machines exist.

What you need

  • An Ubuntu 26.04 LTS server (24.04 works too, but packages are built for 26.04 — that gets a warning).
  • A real machine or a full virtual machine (KVM, Xen, VMware). An LXC or OpenVZ container is refused: a node loads its own firewall, writes sysctls and makes a cgroup per account, and in a container each of those either fails or affects the host.
  • A public IPv4 address on the interface itself. Behind NAT the server can still dial out, but the DNS records CoreCP publishes would point at an address nobody can reach.
  • A fully qualified hostname that matches: hostname -f must answer the same thing you type in the panel. The certificate is issued for that name.
  • Outbound HTTPS to the panel and outbound 9443 to the certificate authority. Nothing needs to be open inbound: the server dials out, never the other way round.
  • For the mail role, outbound port 25 as well — most providers block it by default and open it on request.

Step 1 — say what the server is for

Panel → ServersAdd server (or ⌘K, "Add server").

FieldWhat it does
Fully qualified nameThe identity. The certificate is issued for it.
Friendly nameFor the list only. Optional.
GroupWho may see the machine.
LocationFree text: datacentre, city, rack. Optional.
Rolesweb, db, mail, dns, backup, ftp — several at once is fine.
Server groupWhich pool packages schedule onto. Optional.
Valid for15, 30 or 60 minutes.

The roles travel inside the command. That is the difference from most other panels: the server comes up configured rather than joined-and-empty.

Step 2 — the command

You get one line back, with a visible countdown. On the new server:

# 1 — install the agent (skip if corectl is already there)
curl -fsSL https://get.corecp.dev | bash

# 2 — join (the panel gives you the real code)
corectl join --code corecp1.eyJ2IjoxLCJwIjoiaHR0cHM6Ly9wYW5lbDEu…Q.78d8e8b8

The code is single-use, it expires, and it carries the certificate authority's fingerprint. That is what makes first contact something other than "trust whatever answers": the server pins on that fingerprint before it believes anything the authority says.

You can read a code without contacting anything:

corectl join --show --code corecp1.…
panel        https://panel1.corecp.dev
node         stck2.corecp.dev
roles        web, db
authority    build.corecp.dev:9443
ca pin       c4430ea564db75b6
expires      2026-08-09T16:05:00Z
panel ips    185.117.226.121, 2a10:7180:100::121

Step 3 — watch

From the moment you press enter the card in the panel follows along. Six phases, in this order:

PhaseWhat happens
PreflightThe machine is read. Nothing is written.
HardeningBase packages, sysctls, ssh hardening, fail2ban, nftables.
mTLS enrolmentThe authority signs the node's certificate.
Role packagesThe chosen roles are installed.
ServicesThe agent and the role services come up.
Healthcorectl doctor — "joined" has to mean "working".

On the server you see the same thing:

[corecp] joining https://panel1.corecp.dev as stck2.corecp.dev
[corecp] roles: web, db
[corecp] certificate authority build.corecp.dev:9443, pinned on c4430ea564db75b6…

[corecp] == preflight == checking this machine before anything is installed
  ok   operating system             Ubuntu 26.04 LTS
  ok   architecture                 amd64
  ok   virtualization               kvm virtual machine
  ok   memory                       3.9 GB (db, web needs 2.0 GB)
  ok   port 80/tcp free             HTTP, nothing listening
  ok   panel reachable              https://panel1.corecp.dev answers, outbound is open
  ok   public IPv4                  185.133.89.70, the same address the panel sees
[corecp] preflight: this machine can carry db, web

The preflight

This is the part CoreCP does differently. Other panels document their requirements; here they are checked, before a single package is downloaded. One failing check stops the run, and nothing has been installed.

What is checked: the operating system (Ubuntu LTS), the architecture, whether it is a container, whether ports 80/443 (and 25 for mail, 53 for dns) are free, whether there are remnants of MySQL/Postfix/another control panel, RAM and disk against the floor for each chosen role, a static public IPv4 and NAT detection, outbound reachability to the panel, and time sync. For the mail role also outbound port 25 and the PTR record; for the dns role that 53 is free.

Every check comes back, including the ones that passed. Somebody fixing a machine wants the whole list, not the first thing that went wrong. Every red line carries what to do about it.

You can run the preflight on its own, without joining:

# on the machine itself
corectl node preflight --roles web,db --panel https://panel1.corecp.dev

# or, for a machine that has already joined, from the panel:
#   Servers → <server> → add a role

Exit code 1 means "this machine cannot carry it", so it is usable in a script.

To run only the preflight with the real code, installing nothing:

corectl join --code corecp1.… --dry-run

When it goes wrong

Interrupted half-way. Run the same command again with --retry:

corectl join --code corecp1.… --retry

That is idempotent. Roles that are already there are left alone, and if the authority will not sign the token again — because the attempt that died spent it — the run continues with the certificate that attempt bought.

Mangled paste. The code carries a checksum. A line truncated by a chat window or an email gives:

corectl: this join code is incomplete or was altered in transit

Copy it again from the panel. Line breaks and spaces in the middle are fine — they are stripped.

Expired. The token dies after 15-60 minutes and the command says when it was valid until. Issue a new one in the panel.

Provider firewall. Recognisable as a failing panel reachable or outbound SMTP (port 25). The node dials out; nothing needs to be open inbound. What does need to be open: outbound 443 to the panel, outbound 9443 to the authority, and outbound 25 if you want the mail role.

Start over. In the panel: Servers → the server → danger zone → Forget this server and start over. That revokes the token and the certificate and removes the record. Then, on the machine itself:

corectl join --reset                  # certificate and panel binding gone
corectl join --reset --purge --yes    # also node.yaml: roles and configuration gone

--purge is a separate flag with a confirmation, because node.yaml is not identity but configuration: the roles, the webserver choice, the DNS primaries. On a machine that still hosts accounts that is not a reset, it is a wipe.

Revoke a token without forgetting the server. In the panel, next to the command: Revoke the token. It goes to the certificate authority as well — a revocation the signer does not know about is a button that lies.

After the join

  • Firewall and fail2ban are in place, per role.
  • The node subscribes to the channel the fleet is on.
  • Existing websites do not move. New capacity gets work only when you put the machine in a server group, which is a separate and deliberate act.

Proving this without a spare server

This feature's acceptance run (scripts/e2e-r2-node-wizard.sh) has no disposable VM available — the build server is itself a KVM guest with no nested virtualisation. Part (b) is therefore a real re-enrolment of ns2.corecp.dev through the same flow: a real join code, a real preflight, a real CSR signed by the real CA, the real role phase and the real doctor. The script snapshots the certificate and node.yaml beforehand and puts them back if anything fails; on green the only lasting difference is that the node holds a newer certificate, which is what a join is for.