@@PRODUCT@@

Config drop-ins

Sooner or later a machine needs one setting CoreCP does not model: an

Written for: Administrator

Sooner or later a machine needs one setting CoreCP does not model: an innodb_buffer_pool_size that fits this server's memory, an nginx timeout for a customer's slow importer, a maxmemory policy for the object cache. A drop-in is where that setting goes — in the service's own include directory, written from /etc/corecp like everything else, and applied with a net under it.

In the panel: Servers → the server → Quick actions → Config drop-ins, or directly at /nodes/<server>/dropins.

Which services take one

root@web1:~# corectl dropin list
Config drop-ins:
  apache    —          /etc/apache2/conf-available/zz-corecp-custom.conf  config test
  mariadb   6 lines    /etc/mysql/conf.d/zz-corecp-custom.cnf             health check + rollback
  nginx     —          /etc/nginx/conf.d/zz-corecp-custom.conf            config test
  php       —          /opt/corecp/php/84/etc/conf.d/zz-corecp-custom.ini config test
  valkey    —          /etc/valkey/zz-corecp-custom.conf                  health check + rollback
  fpm       —          /opt/corecp/php/84/etc/fpm.d/zz-corecp-custom.conf config test
  sysctl    —          /etc/sysctl.d/zz-corecp-custom.conf                config test

A drop-in may not set: bind-address, datadir, identity, include, logpath, socket …
Accounts have no drop-ins: PHP goes through the policy, the database through its per-user limits.

The file is always called zz-corecp-custom.*. That is not decoration: an include directory is read in sorted order, and the last file wins — so your setting beats CoreCP's own corecp.cnf and anything an addon dropped beside it.

The PHP drop-in renders once per installed series, into every interpreter's own ini scan directory. A setting that applied to PHP 8.4 and not to 8.3 would be a support case nobody could reproduce.

php or fpm — which of the two?

There are two PHP targets and the difference is worth knowing, because picking the wrong one means nothing happens and nobody says anything.

  • php is the php.ini target: settings the interpreter reads as it starts. Everything to do with OPcache belongs here, because OPcache claims its memory the moment PHP starts — a value handed to a pool per request arrives too late and does nothing.
  • fpm is the pool configuration: how many requests a worker handles before it is replaced, how long the master waits for a worker on a reload, how many processes the machine as a whole can carry.

Since this round, corectl dropin set php-fpm means the pool file; it used to be a second name for php, which is not what it says.

The fpm drop-in is inserted as the last line of every account's pool file, so it wins over CoreCP's own pool defaults — that is the whole contract of a drop-in. What it may not set is what CoreCP models per account: the socket, the user, the cage (open_basedir) and pm.max_children, because that last one is the worker limit from that customer's package. A LiteSpeed server runs lsphp instead of FPM, and this target renders nothing there.

sysctl — the kernel

Ordinary sysctl.d, last in the alphabet so it wins over the distribution's files and over CoreCP's own hardening. Two things are worth knowing:

  • A key this kernel does not have is refused before the file is written. systemd-sysctl marks the whole file as failed as soon as one key in it is unknown, so one typo would silently cost every other setting in it.
  • The health check reads every value back. There is no service to ask whether it is happy, so the only honest proof is comparing what the kernel reports with what you asked for — and the same check proves a rollback landed.
root@web1:~# corectl dropin set sysctl --file - <<'EOF'
vm.swapiness = 10
EOF
corectl: this kernel has no sysctl called vm.swapiness (line 1). systemd-sysctl
reports one unknown key as a failure for the whole file, so the rest of this
drop-in would not be applied either

Writing one

The text never travels on the command line — a configuration block is multi-line, and /proc/<pid>/cmdline is world-readable. So it comes from a file or from standard input:

root@web1:~# cat > /tmp/tuning.cnf <<'EOF'
[mysqld]
innodb_buffer_pool_size = 2G
max_connections = 300
EOF

root@web1:~# corectl dropin set mariadb --file /tmp/tuning.cnf --note "8 GB machine, 2026-08"
drop-in mariadb applied (/etc/mysql/conf.d/zz-corecp-custom.cnf)

--note becomes a comment line at the top, so the reason stays next to the setting instead of in a ticket.

What happens when you apply

  1. The forbidden list. Every line is read before anything is written.
  2. Placed atomically. A temporary file, then a rename — no service ever reads a half-written file.
  3. The service's own config test, where it has one: nginx -t, apachectl configtest, and for PHP the ini parser itself, pointed at the candidate. A failure puts the previous file back and the running service never saw the new one.
  4. Applied. nginx and Apache reload; MariaDB, Valkey and the FPM pools restart, because they read their configuration once.
  5. The health check. MariaDB has to answer, Valkey has to say PONG, the web server has to be running.
  6. The automatic rollback, when step 5 fails: the previous file goes back, the service is restarted again, and the change is reported as refused. The text you sent is not saved — a file in /etc/corecp that the machine is not running would be exactly the second source of truth this engine avoids.

MariaDB and Valkey have no config test at all — this family of servers has never had one, and mysqld --validate-config only arrived in MariaDB 13.1 while Ubuntu 26.04 ships 11.8 (we measured it: on 11.8 the flag is silently ignored). For those two, steps 5 and 6 are the whole net, which is why they are there for every service and not only for the ones without a validator: a configuration that tests clean and a daemon that then refuses to start is not hypothetical.

root@web1:~# corectl dropin set mariadb --file /tmp/too-big.cnf
[corecp] the health check for mariadb failed after the drop-in was placed — rolling back
[corecp] rolled back: mariadb is running on its previous configuration
error: the drop-in was placed and mariadb did not stay healthy (…), so it was rolled
back automatically. mariadb is running again on the previous configuration; the
refused text is not saved

What a drop-in may never set

corectl refuses these by name, and says what would break:

ClassWhy
socketCoreCP renders the sockets services reach each other on. Moving one leaves the other half pointing at a path nothing serves.
identityThe user and group a service runs as decide which files it can read. CoreCP sets them per account and per pool.
bind-addressAddresses come from the IP manager and the firewall. A bind here is invisible to both, and the usual result is a database reachable from the internet.
datadirWhere the data lives is what the backups, the restores and the disk accounting read. A server that moved its data directory keeps running and gets backed up empty.
includeA further include takes the configuration somewhere this machine's state does not describe.
logpathThe statistics, the log viewer and the log rotation read the paths CoreCP renders. A log that moved is a log nobody rotates.

fpm and sysctl add classes of their own, because what they may not move is different:

ClassTargetWhy
account-limitfpmHow many workers a pool may run is the limit from that customer's package. One number here gives every account on the machine the same limit while the panel goes on showing the package's.
cagefpmThe cage a pool runs in — its root, its working directory, open_basedir, the temporary directories — is rendered per account from that account's home.
sectionfpmThis file is included inside a pool that already exists, and FPM allocates a new pool for every section header it reads — even for the same name. A header here makes the machine's whole PHP refuse to start. Write pool settings with no header; [global] is for the master itself.
network-planesysctlWhich addresses this server answers on comes from the IP manager and the firewall. A kernel that quietly stopped speaking IPv6 leaves every published AAAA record pointing at a machine that no longer listens.
isolationsysctlEvery account's PHP pool runs in a systemd sandbox that needs these namespaces. Setting them to zero does not harden the machine, it stops every website from starting.
identitysysctlThis server's name is the name on its certificate and how the panel reaches it.
root@web1:~# corectl dropin set mariadb --file /tmp/bad.cnf
error: this drop-in sets 2 thing(s) a drop-in may not set:
  line 2: datadir = /srv/elsewhere — where the data lives is what the backups, …
  line 3: bind-address = 0.0.0.0 — the addresses a service answers on come from …

This is not a security boundary — an administrator with a root shell can edit any file on the machine. It is the boundary between a setting CoreCP does not model and a second, invisible source of truth for one it does. The same check runs at every reconcile, so a file edited by hand on the machine is reported and not rendered rather than silently applied.

Going back

The five previous versions stay on the machine, under /etc/corecp/rendered/dropins/<service>/:

root@web1:~# corectl dropin history mariadb
Kept versions of the mariadb drop-in (newest first):
  1   4 lines    112 bytes  [mysqld]
  2   6 lines    168 bytes  [mysqld]

Restore one with: corectl dropin rollback mariadb --version <n>

root@web1:~# corectl dropin rollback mariadb --version 1
drop-in mariadb applied (/etc/mysql/conf.d/zz-corecp-custom.cnf)

Removing the drop-in altogether puts the service back on CoreCP's own configuration, and keeps the text as a version:

root@web1:~# corectl dropin remove valkey
the valkey drop-in is gone; valkey-server runs on CoreCP's own configuration again

Accounts have none of this

There is no per-account drop-in and there will not be one. A customer changes PHP through the PHP policy — an allow-list with ceilings, where every value is checked before it is rendered — and their database through the per-user limits (MAX_USER_CONNECTIONS and friends). Raw configuration text from an account would be a directive nobody validated, in a file every other account's service reads.

Drop-ins are server-admin and up, in the panel and on the API alike. A reseller who reaches the routes gets 403 from the router, not from a check somebody remembered to write.