How YunoHost Isolates Applications: System Users, Subdomains, and Where the Boundaries Actually Sit
YunoHost isolates apps with one Unix user and one subdomain each. This note maps where that boundary is real, where it is weak, and how to verify it with ordinary Linux tools.
22 Jul 2026, 15:00 UTC

If you run several self-hosted apps on one YunoHost server, the question that matters is not whether they are isolated but where the isolation actually lives. YunoHost's answer is deliberately modest: each application gets its own Unix system user and its own subdomain (or path) on the host. That is process- and filesystem-level separation, not containerization — and understanding that distinction tells you both what you can trust and what you still need to check yourself.
Requirements the design has to meet
A multi-app server needs four things from an isolation scheme. First, one compromised or buggy app must not be able to read or modify another app's files. Second, apps must not collide on ports, database names, or system resources. Third, an administrator must be able to reason about the boundary with ordinary Linux tools, because YunoHost targets small self-hosters, not platform teams. Fourth, the mechanism has to survive app installs and removals without manual cleanup. Containers or VMs would satisfy the first two more strongly, but at a memory and complexity cost that conflicts with running on a small VPS or a home board.
The smallest design that works
YunoHost's actual mechanism has two halves:
- One system user per app. Installing an app creates a dedicated Unix user, and the app's code and data live under directories owned by that user (conventionally
/var/www/<app>for the code and a per-app home for data). The app's processes — PHP-FPM pool, Node service, Python worker — run under that UID. Standard Unix file permissions then do the enforcement: app A's process literally cannot open app B's files unless permissions say otherwise. - One subdomain (or path) per app. The nginx reverse proxy routes
app1.example.organdapp2.example.orgto different upstream services. This gives each app its own cookie scope, its own TLS certificate via Let's Encrypt, and a clean URL namespace, so apps never fight over routes.
Supporting pieces fill the gaps: each app gets its own database or database user in the shared MariaDB/PostgreSQL instance, its own PHP-FPM pool where relevant, and its own systemd service unit. Nothing here is exotic — that is the point. Every layer is inspectable with ls, ss, and systemctl.
Where the trust boundaries actually are
It helps to be precise about what is and is not a boundary:
- Filesystem: real boundary. UID separation plus default permissions means one app's process cannot read another's uploads, config, or secrets. This holds as long as permissions were set correctly at install time and nobody loosened them later.
- Network/HTTP: partial boundary. Subdomains separate cookies and TLS, but all apps share one nginx and one IP. A misconfigured vhost or a DNS record pointing somewhere unexpected can route traffic to the wrong place. The subdomain scheme assumes your DNS is correct; it does not enforce it.
- Database: weak boundary. Apps share one database server. Isolation depends entirely on each app having its own credentials with grants limited to its own database. If an app stores its DB password in a world-readable file, or an installer grants overly broad privileges, the filesystem boundary does not save you.
- Kernel/resources: no boundary. There are no cgroups-enforced limits by default. A runaway app can consume all CPU or memory and starve the others. Availability is not isolated even though data is.
Operational checks worth running
These checks run on the YunoHost host itself as root (or via sudo). They read state only; they change nothing.
1. Confirm each app's files are owned by its own user.
ls -l /var/www/
ls -ld /home/yunohost.app/*Each app directory should show a distinct owner matching the app name. Two apps sharing a UID, or files owned by root or www-data inside an app's directory, are findings worth investigating — the first breaks isolation, the second often means a botched manual edit.
2. Confirm processes run as the right user.
ps -eo user,comm,args | grep -E 'php-fpm|node|python'Each app's workers should appear under its dedicated user, not a shared account.
3. Confirm routing resolves to the right app.
curl -sI https://app1.example.org | head -5
ls /etc/nginx/conf.d/Each subdomain should return a response from its own app, and there should be one nginx config per domain/app. A subdomain answering with a different app's content indicates a routing or DNS mistake.
4. Spot-check database privileges. From the MariaDB root shell, SHOW GRANTS FOR 'app1'@'localhost'; should show rights scoped to that app's database only — not *.*.
Failure modes to watch for
- Permission drift. A manual
chmod -R 777to "fix" an upload error silently opens the directory to every other app on the box. Fix the ownership instead:chown -R app1:app1 /var/www/app1. - DNS mistakes. A wildcard record or a stale entry pointing a subdomain at the wrong host bypasses the routing layer's assumptions. Verify with
dig app1.example.orgfrom an external network, not just from the server. - Shared-service leakage. Redis, the database server, or a mail queue used by multiple apps is only as isolated as its own authentication. An app that can reach a shared service without credentials can reach every app's data in it.
- Resource exhaustion. One app's memory leak takes down all of them. Watch
systemctl statusand OOM-killer messages injournalctl -k.
When this design stops being enough
The per-user model is the right size for a trusted set of mainstream apps on a personal server. Reconsider it when: you host apps you do not trust (arbitrary user-supplied code, where you want container or VM boundaries and kernel-level syscall filtering); you need guaranteed resource shares per app (add cgroup limits via systemd unit overrides, or move to containers); a single app's compromise would be catastrophic for the others (e.g., mixing a public pastebin with your password manager — put them on separate machines); or you need path-based multi-tenancy at a scale where per-app subdomains become a DNS management burden. In those cases YunoHost's isolation is a convenience layer, not a security perimeter, and should be treated accordingly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.