@@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.
  • A group in the panel. A server always belongs to a group; a fresh panel has none yet, and the wizard then says so at the top with a Go to Groups button. Create one there with Create group.
  • 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.
Server addressesThe IPv4 and/or IPv6 address the server connects from. Optional when the name already points at the server.
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.

Why the panel wants the server's address

A new server gets its certificate from the panel's certificate service, on port 9443. That port is closed to everybody except the servers you invite here. When you press Issue code, the port opens for this server's addresses — until the code expires. After the join they stay admitted, because the server renews its certificate there every few weeks.

  • Leave the field empty and the panel uses what it already knows about the server, or else what its name points to. The next screen shows which addresses that became, under Admitted addresses.
  • If the name does not point anywhere yet, the field says so. Enter the address the server goes out from — behind NAT that is the router's address, not the server's own.
  • Revoke the code with Revoke the token and the port closes for that server straight away.
  • Retire a server and it loses both its certificate and its access to the port.

If you see None — the certificate service has no firewall of its own, an administrator on the panel server chose corectl ca firewall off on purpose, because a firewall in front of it (your provider's, for example) already limits port 9443. Open the port there for your servers' addresses only.

If the wizard refuses every new code with a message about the firewall, the panel server's firewall rules are not loaded. The installation never switches them off by itself; load the rules (nft -f /etc/nftables.conf) and run bash /usr/lib/corecp-panel/setup.sh finish.

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.

From a script, hand the code over on standard input instead of on the command line — a command line is visible in the process list for as long as the join runs:

printf '%s\n' "$CODE" | corectl join --code-stdin

--code and --code-stdin together are refused, and so is an empty input. The older corectl agent join <ca-host> --token … has the same form, --token-stdin.

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

The check before installation: DNS, PTR and glue

The preflight above looks at the machine. Whether the world sees the machine correctly, it checks only in part: it compares the PTR for the mail role only, and only as a warning. When you set up a series of servers at once, such as a new production environment, run the standalone check from the repository first. It reads nothing from a panel and changes nothing on the server:

bash production-preflight.sh --fqdn web1.example.net --ipv4 192.0.2.10 \
     --ipv6 2001:db8::10 --roles web,dns-primary --ns ns1.example.net

Per server it checks the name, the operating system, whether it is a VM (and whether the guest agent runs), processors, memory and disk per role, the clock, whether the addresses sit on an interface, the private network, the A and AAAA records at every authoritative name server, the PTR with the mail name and whether that name points back, the glue of your name servers at the registry, and whether outbound port 25 is open. Every line is green or red, with the reason.

The check always asks the real resolver, never the local stub on 127.0.0.53. That stub answers the machine's own host name and addresses from local configuration, even when nothing exists in DNS, so a PTR check through the stub is always green.

The full plan for a new production environment is in the owner's runbook docs/operations/productie-opzetten.md (Dutch).

When it goes wrong

The server cannot reach the certificate service. The join stops in the enrolment phase, saying the service admits only the addresses from the wizard. The server is going out from another address than the one you gave. Revoke the code, enter the right address and issue a new one.

A joined server got a new address. It keeps working but can no longer renew its certificate. On the panel server:

corectl ca firewall show
corectl ca firewall admit --name web1.example.net --address 192.0.2.20

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.

The installer shows what a server will trust

The curl -fsSL https://get.<domain> | bash line the wizard shows belongs to this panel's package source: the build server on staging, the production panel in production. At that address the installer reads which package source and which keys the server is to trust and shows their fingerprints before it writes anything; compare them with your runbook. They are then in the server's settings, and corectl update trust shows them on the server. The example name in the name field (web2.example.net) is an example.