.env (MySQL root password, app key), cannot use the panel’s sudo rule, and cannot reach root through the panel.
Who runs what
Two groups tie it together:
laravelify-sites: the panel and nginx. Every site folder belongs to this group, so they can read it. A website’s user is never in it, so websites cannot read each other.laravelify-tenants: every website’s own user, and nobody else. The panel may run commands as its members (sudo -u lfy-<site>).
lfy- followed by its slug (lfy-shop for shop). Slugs longer than 28 characters are shortened and end with a short hash, so the name stays within Linux’s 32 characters.
Why each piece is there
- Each website has its own user. With one shared user, a hacked site could rewrite every other site’s code. Now its user can only touch its own folder: other site folders are closed to it (
750), and/var/wwwitself belongs to root, so it cannot rename another site’s folder either. - Each website has its own PHP-FPM pool. PHP runs as the site’s user. The pool’s socket can only be opened by nginx, so one site cannot send FastCGI requests to another site’s pool (or the panel’s) and run code as it. Pools start PHP only when a request comes in (
pm = ondemand), so an idle site costs no memory. - Each website has its own HOME, caches, trash, and temp folder in
/var/lib/laravelify/sites/<site>. A shared Composer or npm cache would let a hacked site plant a package that another site’s next deploy installs. - Deploys run as the website’s user. A deploy runs your repository’s code: Composer scripts, npm
postinstall,artisan, git hooks. The panel starts them withsudo -u lfy-<site>, with a clean environment, so they never run as the panel. - The file manager changes files as the website’s user. Writes, uploads, renames, moves, deletes, permissions, zips, and restores go through
sudo, so every file stays the site’s own. Reading needs no help: the panel is inlaravelify-sites. Files cannot be moved or copied between two websites in one step; download them from one and upload them to the other. - New files keep the group. Site folders have the setgid bit, so files a site creates belong to
laravelify-sitesand nginx can serve them. Each deploy puts back the owner and group of anything that lost it. - nginx has its own user. If nginx ran as a site’s user, that site’s code could talk FastCGI straight to the panel’s PHP-FPM pool and run any PHP file as the panel.
- The panel does not use Redis. Redis has no password by default and every site can reach it. The panel keeps its sessions, queue, and cache in its MySQL database. Your sites can keep using Redis.
- phpMyAdmin runs in the panel’s pool. Its sessions and config are out of reach of website code.
- Node apps run in their website’s own PM2 daemon.
pm2-lfy-<site>restores it after a reboot. PM2’s app files are in/var/lib/laravelify/pm2, not in the site folder, because PM2 runs them as code; each is readable only by its own website.
The terminal
- In a website (its Terminal tab, or a site folder in Tools → Terminal), commands run as that website’s user, can write only in its folder, and stop after 60 seconds.
- In
/var/wwwitself (Tools → Terminal at the top), commands run asnobodyand cannot write anything or look inside the websites. A user that could read every website would let any website’s code read all the others.
What host-apply.sh checks
scripts/host-apply.sh is the only thing the panel can run as root. It accepts a fixed list of actions and validates every argument. It never takes a website’s user from the panel: a site has its own user when its folder belongs to lfy-<site>, which only root can arrange. It also checks every file it installs:
- Rendered files must come from the panel’s own
storage/(owned bylaravelify, writable by nobody else). - nginx files of a site may only log to
/var/log/nginxor/var/log/laravelify, include files under/etc/nginx, keep temp and cache folders under nginx’s own, and send PHP to the site’s own pool.load_module,user, Lua, Perl, and njs are refused. - Supervisor files may only hold
[program:<site>-…]sections. Every program runs as the website’s user and logs into/var/log/laravelify/sites/<site>/. The panel fills in both when you save, so older configs keep working. - A website’s PHP-FPM pool is written by the script itself from fixed values; nothing in it comes from the panel.
- The Cloudflare tunnel unit is written by the script itself; only the token comes from the panel.
- Root never writes through a path inside
/var/www: the site’s.env, its log folders, and the “turned off” marker are written as the website’s user, so a link planted by a site cannot point root at another file.
Files and folders
Websites not moved to their own user yet keep the older layout: their folder belongs to
www-data, and they share /var/lib/laravelify/home and /var/lib/laravelify/trash.
Running artisan for the panel
Run the panel’s artisan as its user:Moving websites to their own users
New websites get their own user from the start. Websites made before the update keepwww-data until you move them. Move all of them:
- Creates
lfy-<site>and its folders in/var/lib/laravelify/sites/<site>. - Gives the site folder to it, and moves the site’s items from the shared trash to its own.
- Writes the site again: PHP moves to its own pool, workers run as its user, and its Node app moves to its own PM2 daemon.
- Checks that the site still answers. A site that answered before and does not answer after goes back to
www-datastraight away, and the command says so.
Moving an older server over
Updating from 0.3.0-beta.39 or older gives the panel its own user by itself. The update:- Saves the current files to
/var/lib/laravelify/rollback/before-privilege-separation. - Creates
laravelify,laravelify-web, and the groupslaravelify-sitesandlaravelify-tenants. - Lets a running deploy finish, then moves the panel to its own PHP-FPM pool, queue worker, and scheduler.
- Moves the panel’s sessions, queue, and cache from Redis to MySQL. Everyone signs in to the panel again once.
- Applies every site’s workers again: Supervisor programs as
www-datawith logs in/var/log/laravelify/sites, and Node apps from root’s PM2 towww-data’s.
502 for a few seconds during the switch. Each Laravel worker and Node app restarts once. Websites are not reloaded otherwise. Then move the websites to their own users as above.
If a site’s workers could not be applied (for example a custom Supervisor section the checks refuse), it keeps running as before and the update says which. Fix it on the site’s Processes tab, then run:
scripts/privileges.sh is safe to run any time; it puts users, permissions, and services back the way this page describes.
Rolling back
Give one website back towww-data:
www-data, its PHP runs in the shared pool of its PHP version, its workers and Node app run as www-data, and lfy-<site> is deleted. Folders, databases, and settings stay. To turn per-site users on again, delete /var/lib/laravelify/.no-site-users and move the websites as above.
If the panel’s own user misbehaves:
www-data, puts the saved files back, and runs the panel as www-data again (which reopens the holes this page closes). The second installs the older release. To separate again later, delete /var/lib/laravelify/.no-separation and run scripts/privileges.sh.