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
| Key | Maps to | Meaning |
|---|---|---|
CPU_QUOTA | systemd CPUQuota | Percentage such as 50%, or absolute Xms / period. 100% is one full core. |
MEMORY_MAX | systemd MemoryMax | 512M, 1G, or raw bytes. Hard ceiling: the tenant's processes are OOM-killed above it, the host is not. |
TASKS_MAX | systemd TasksMax | Maximum PIDs in the slice. This is your forkbomb defense. |
IO_WEIGHT | systemd IOWeight | 1 to 10000, relative weight, default 100. Weight, not a hard cap. See the note below. |
ENTRY_PROCESSES | PHP-FPM pm.max_children | Concurrent PHP request slots for this tenant. |
MySQL caps
| Key | Meaning |
|---|---|
MYSQL_MAX_QUERIES_PER_HOUR | Enforced by MariaDB itself via GRANT ... WITH MAX_QUERIES_PER_HOUR. |
MYSQL_MAX_USER_CONNECTIONS | Enforced by MariaDB itself via GRANT ... WITH MAX_USER_CONNECTIONS. |
MYSQL_PROXYSQL_DELAY_MS | Per-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.