@@PRODUCT@@

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. See Which Ubuntu below: 24.04 still joins, with a warning, but is outside support; any other release refuses to install.
  • 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.

Which Ubuntu

One release is supported, one still joins, and the rest are refused:

releasewhat happens
Ubuntu 26.04 LTSsupported. Everything else in this handbook is about this.
Ubuntu 24.04 LTSinstalls and joins, with a warning on both doors. It is outside support.
anything elsethe install stops. CORECP_FORCE=1 overrides that, but then you are running something nobody measures.

24.04 is not refused because it is still what providers image by default. A refusal on a machine where the packages install perfectly well is a refusal nobody believes — and works around. A warning that says what it costs gets read.

Outside support is a specific promise, not a shade of grey. On 24.04 the packages install (they depend on systemd and nothing else), the server joins, and the panel manages it — and none of that is covered. Bugs are not accepted against it, the PHP and MariaDB series are built against 26.04's libraries, and the next thing that changes in either may simply stop working there. It is a door left open for a machine on its way to 26.04, not a second platform.

You are told twice: the panel says it beside the command you are about to paste, and the machine says it in its own preflight (warn operating system … outside CoreCP support). The full matrix is in docs/versions-policy.md.

Step 1 — say what the server is for

Panel → Servers → Add 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.
ProfileThe shape of the machine. Fills in the roles and the web server below.
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.

The backup role picks a destination

Tick backup among the roles and one extra field appears: Backup destination. It lists the storage you have already recorded, so you do not have to retype an address, a path and a password here.

At this moment the machine does not exist yet — you have a name and a join command and nothing else — so the choice is recorded against the server and nothing is sent. Once the machine answers, its own backup page says it still has to be attached, with the button beside it.

If you have no destination yet the screen says so too: the server gets the role, and where it writes is chosen later. See Your servers' backups.

You do not have to decide here at all. Once the machine has joined, the last step of this wizard shows the panel's own recommendation — which destination this machine should write to, with the five measurements it was ranked on, and one button to attach it. The same card is on the machine's own backups page for as long as nothing is recorded, so leaving this field empty costs nothing.

What happens to a new account on this server

At the bottom of step 1 is a card that reads the placement out loud, before you are given a join command at all:

web + dns here · database on stck2.corecp.dev · mail on stck2.corecp.dev

That is not a description of the rules but the answer itself: the same placement engine that will place the first account, with this machine already in it. Underneath it, one row per role with the server that carries it and the reason the engine gave ("fewest websites in server group eu-shared").

If a choice is wrong, fix it per role with the picker beside it and the card works it out again — so what you see is what will really happen, including the case where your choice cannot be used and the pool takes the role back.

Two roles have no picker. Web does not, because the account is created on this machine and its files are here. DNS does not, because a nameserver set is enumerated rather than chosen: a zone is served by every nameserver in the pool, and fixing one would shrink the set to a single machine.

When a role picks another server, the panel arranges the rest on the first account by itself: that machine is told this server may reach it, the firewall opens for exactly this address, and the account's database logins are given access from it. There is nothing for you to do.

Start from a profile

The form opens with a profile: shared for an all-in-one machine, db-dedicated for a database server, mail-dedicated, dns-edge, backup-target, wordpress. Choosing one shows you what it is for and what it tunes, and fills in the roles and the web server below.

You can still change all of it — a profile is a starting point, not a cage. Tick a role on or off and the screen says the machine no longer matches the profile, and it is enrolled with exactly the choices on screen. It is then not stamped with the profile name, because a machine carrying the name of a profile it does not match would report drift from its first minute. Giving it the profile later is always possible, on the server's Configuration tab.

If you would rather just tick the roles yourself, choose Custom.

When the profile does travel, the server sets itself up from it — including the tools, the config drop-ins and the ceilings that come with it:

root@stck2:~# corectl join --code corecp1.… --show
panel        https://panel1.corecp.dev
node         stck2.corecp.dev
roles        db
profile      db-dedicated  (the roles above come from it)
authority    build.corecp.dev:9443

See also the Server profiles article.

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".

Every phase carries a coloured dot, and the preflight carries one per check:

ColourWhat it means
GreenPassed.
PurpleThis is what it is doing now.
AmberA warning — it continues, but read it.
RedThis is where it went wrong; the reason is below.
GreyNot reached yet.

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.

Step 4 — what is this machine going to do?

The moment the progress card reports succeeded, a fourth card appears: What is this machine going to do? It names the roles the machine carries, says which server group it is in — on a fresh machine: none — and gives you one chooser and one button to put it in a pool.

That is exactly the sentence below, turned into an action. Until this session that sentence was in this handbook and the only button for it was step 1 of the wizard: skip the server group there and the machine stayed outside every pool for good, with the command line as the only way back.

Do this later is a real answer and is offered as one. A platform whose machines all do the same thing needs no pools at all — every plan schedules across the whole node group, and that works. If there are no pools yet, the card says so and points at Servers → Placement pools.

Later on, without the wizard:

curl -s -b jar -X POST https://panel1.corecp.dev/api/v1/nodes/stck2.corecp.dev/pool \
  -H 'Content-Type: application/json' -d '{"server_group_id":"<pool>"}'

See Where a website is served for what a pool is and what its policy does.

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.

Still to connect

Under the previous card a Still to connect card appears: two settings that did not fit inside the join command and are only possible once the machine answers.

Outbound mail. Send this server's outbound mail through another server — the fleet's mail gateway, for example. Without a relay it delivers on port 25 itself. If the relay wants a login, fill it in beside the host; the password travels to the machine in the request and never comes back out of the panel. From the command line it is the same thing:

root@stck1:~# corectl mail smarthost set mail1.corecp.dev
[corecp] smarthost set to [mail1.corecp.dev]
root@stck1:~# corectl mail smarthost show
smarthost: [mail1.corecp.dev]
user:      none (no SASL)

If this machine does not carry the mail role the card says so: it delivers directly, and a relay can only be set once it carries the role.

Nameserver. A machine carrying the dns role serves nothing until it is in a nameserver set. The button takes you straight there; see Shared nameservers.

The card also repeats where an account's other services land, so you see it again when you open this page later.

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.