Architecture & layout

tulixhost runs every site under its own system user. nginx and PHP-FPM are shared daemons, but each site has a dedicated FPM pool socket, MariaDB user, and Redis ACL. There is no global "web" user that owns everything.

Per-site directory tree

Every site lives under /data/web/<site>/. The layout is fixed because all scripts and the demo dashboard rely on it.

/data/web/example.com/
├── public/              ← webroot, served by nginx
│   └── index.php
├── conf/
│   ├── .envtulix        ← secrets — mode 0640, root:web_example_com
│   ├── php.ini          ← soft PHP settings (editable by site owner)
│   ├── php-fpm.conf     ← symlink to /etc/php/<ver>/fpm/pool.d/example.com.conf
│   ├── my.cnf           ← client + session caps for MariaDB
│   ├── redis.conf       ← db number, prefix, soft limits
│   ├── nginx-extra.conf ← per-site nginx directives (included from the vhost)
│   └── ssl/
│       ├── example.com.crt
│       └── example.com.key
├── logs/
│   ├── nginx-access.log
│   ├── nginx-error.log
│   ├── php-error.log
│   ├── php-fpm-slow.log
│   ├── php-fpm-access.log
│   └── app.log          ← your application's log (optional)
├── tmp/                 ← PHP sessions, FPM socket, upload tmp, sys_temp_dir
│   └── php-fpm.sock
├── cron/
│   └── crontab          ← editable source for /etc/cron.d/tulixhost-example.com
└── backups/             ← reserved (site-local; actual backups go to /data/backups/)

Per-site system user

For a site named example.com, the system user is web_example_com. Dots and dashes are replaced with underscores; the web_ prefix is fixed. The user is created with:

useradd --system --no-create-home \
        --home-dir /data/web/example.com \
        --shell /usr/sbin/nologin web_example_com

www-data is added to the per-site group so nginx can read static files. The site's PHP-FPM pool runs as the per-site user, so:

Shared daemons

daemonconfigper-site bit
nginx/etc/nginx/sites-available/<site>.conf
PHP-FPM/etc/php/<ver>/fpm/pool.d/<site>.conf (one pool per site)
MariaDB/etc/mysql/one database + one user per site
Redis/etc/redis/redis.confone ACL user + one DB number + one key prefix

Where the toolkit itself lives

/etc/tulixhost/tulixhost.confMain config — PHP version, Redis admin pass, retention.
/usr/local/sbin/tulixhost-*Symlinks to the local *.sh scripts.
/var/log/tulixhost/audit.logEvery privileged invocation.
/var/log/tulixhost/backup.logOutput of the nightly backup_all cron.
/etc/cron.d/tulixhostThe master nightly backup cron.
/etc/cron.d/tulixhost-<site>One file per site with cron entries.
/etc/logrotate.d/tulixhostRotation for site logs.
/etc/nginx/snippets/tulixhost-security.confGlobal security headers + dotfile deny.
/etc/nginx/snippets/tulixhost-tls.confTLS defaults (Mozilla intermediate).
/etc/nginx/conf.d/00-tulixhost-default.confCatch-all returning 444 to unknown hostnames.
/etc/mysql/mariadb.conf.d/99-tulixhost.cnfServer-wide MariaDB hardening.
/etc/redis/users.aclRedis ACL file — one line per site.

Why the layout matters

Everything for one site is under one path. That means: