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 Redis maxmemory policy. 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
redis — /etc/redis/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.
phpis thephp.initarget: 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.fpmis 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-sysctlmarks 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 eitherWriting 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
- The forbidden list. Every line is read before anything is written.
- Placed atomically. A temporary file, then a rename — no service ever reads a half-written file.
- 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. - Applied. nginx and Apache reload; MariaDB, Redis and the FPM pools restart, because they read their configuration once.
- The health check. MariaDB has to answer, Redis has to say
PONG, the web server has to be running. - 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/corecpthat the machine is not running would be exactly the second source of truth this engine avoids.
MariaDB and Redis have no config test at all — Redis 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 savedWhat a drop-in may never set
corectl refuses these by name, and says what would break:
| Class | Why |
|---|---|
socket | CoreCP renders the sockets services reach each other on. Moving one leaves the other half pointing at a path nothing serves. |
identity | The user and group a service runs as decide which files it can read. CoreCP sets them per account and per pool. |
bind-address | Addresses 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. |
datadir | Where 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. |
include | A further include takes the configuration somewhere this machine's state does not describe. |
logpath | The 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:
| Class | Target | Why |
|---|---|---|
account-limit | fpm | How 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. |
cage | fpm | The cage a pool runs in — its root, its working directory, open_basedir, the temporary directories — is rendered per account from that account's home. |
section | fpm | This 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-plane | sysctl | Which 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. |
isolation | sysctl | Every 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. |
identity | sysctl | This 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 redis
the redis drop-in is gone; redis-server runs on CoreCP's own configuration againAccounts 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.