Skip to main content

Package profiles

A package profile is the tenantd equivalent of a hosting plan. It defines the resource ceiling for every tenant assigned to it.

Profiles live in /etc/tenantd/packages/<name>.conf and are plain shell-style KEY=value files.

The shipped profiles

tenantd ships two: default (a tight starter shared-hosting tier) and burst (a higher tier, useful for verifying that profile swaps actually change runtime limits).

ls /etc/tenantd/packages/

Keys

Slice limits

KeyMaps toMeaning
CPU_QUOTAsystemd CPUQuotaPercentage such as 50%, or absolute Xms / period. 100% is one full core.
MEMORY_MAXsystemd MemoryMax512M, 1G, or raw bytes. Hard ceiling: the tenant's processes are OOM-killed above it, the host is not.
TASKS_MAXsystemd TasksMaxMaximum PIDs in the slice. This is your forkbomb defense.
IO_WEIGHTsystemd IOWeight1 to 10000, relative weight, default 100. Weight, not a hard cap. See the note below.
ENTRY_PROCESSESPHP-FPM pm.max_childrenConcurrent PHP request slots for this tenant.

MySQL caps

KeyMeaning
MYSQL_MAX_QUERIES_PER_HOUREnforced by MariaDB itself via GRANT ... WITH MAX_QUERIES_PER_HOUR.
MYSQL_MAX_USER_CONNECTIONSEnforced by MariaDB itself via GRANT ... WITH MAX_USER_CONNECTIONS.
MYSQL_PROXYSQL_DELAY_MSPer-query delay in milliseconds, applied through ProxySQL. Requires tenantd-proxysql and tenantctl proxysql-enroll <user>. Default 0 means no throttle.

The MySQL caps are applied as MariaDB grants rather than enforced by the tenantd daemon. That is deliberate: the limits survive even if the governor daemon is stopped, crashed, or not installed.

0 does not mean unlimited. In MariaDB grant language, MAX_QUERIES_PER_HOUR 0 means the tenant is locked out of their own database. tenantctl apply refuses values below 60 unless you pass --allow-zero, because operators reasonably expect 0 to mean "no limit" and it does not.

The default profile

CPU_QUOTA=50%
MEMORY_MAX=512M
TASKS_MAX=200
IO_WEIGHT=100
ENTRY_PROCESSES=10

MYSQL_MAX_QUERIES_PER_HOUR=3600
MYSQL_MAX_USER_CONNECTIONS=20

MYSQL_PROXYSQL_DELAY_MS=0

3600 queries per hour is about one query per second sustained. That is comfortable for a healthy WordPress site plus admin traffic, and not enough for an abuser to starve their neighbors.

Writing your own

Copy an existing profile and edit it:

cp /etc/tenantd/packages/default.conf /etc/tenantd/packages/business.conf
$EDITOR /etc/tenantd/packages/business.conf

Assign it:

tenantctl apply alice --package=business

Re-running tenantctl apply against an already-enrolled tenant is how you move them between profiles. It is safe and takes effect immediately, without dropping their processes.

About IO_WEIGHT

IO_WEIGHT is a relative weight, not a hard cap. A tenant at IO_WEIGHT=300 gets roughly three times the IO share of a tenant at 100 when both are competing. If nobody else is competing, they get everything available.

This works well for per-tenant filesystem IO. It does not give you per-thread IO throttling inside a shared mysqld, because cgroup v2's io controller is domain-only. That limitation is structural, not a tuning problem, and we will not paper over it. See the MySQL governor for how tenantd handles database IO instead.

Where profiles come from after a CloudLinux migration

tenantctl-migrate generates profiles named cl-<signature> from your existing LVE caps. Tenants with identical LVE caps collapse into one shared profile rather than producing one file per user. You can rename and consolidate them afterwards. See migrating from CloudLinux.