关键在于分层建立不可绕过的访问边界:进程级用systemd PrivateTmp隔离/tmp,服务级按PHP-FPM pool配置独立上传目录,用户级依赖XDG_RUNTIME_DIR与sticky bit,数据级结合PostgreSQL RLS、独立Schema及应用层tenant_id过滤与缓存前缀。

实现服务器多租户权限隔离,关键不是给每个用户配个独立账号再 chmod 一下,而是分层建立不可绕过的访问边界——从进程级、服务级、用户级到数据级,每一层都得有对应机制兜底。
服务进程用 systemd PrivateTmp 隔离
Web 服务、定时任务、后台守护进程这类长期运行的服务,是临时文件泄露的高危入口。systemd 提供了开箱即用的隔离能力:
- 在服务单元文件(如
/etc/systemd/system/nginx.service)的[Service]段添加PrivateTmp=yes - 无需手动建目录或改权限,systemd 启动时自动挂载专属 tmpfs 到该服务所有进程的
/tmp和/var/tmp - 验证方式:查该服务任一进程 PID,执行
sudo nsenter -t PID -m -p ls /tmp,只应看到本服务产生的内容
PHP-FPM 按 pool 配置独立上传目录
共用 /tmp 或 NFS 路径会导致上传文件名冲突、覆盖,甚至被其他租户读取:
- 在每个 pool 配置中(如
/etc/php/8.2/fpm/pool.d/client-a.conf)显式指定:php_admin_value[upload_tmp_dir] = /var/tmp/php-client-a - 提前创建目录并严格赋权:
mkdir -p /var/tmp/php-client-a && chown www-data:www-data /var/tmp/php-client-a && chmod 700 /var/tmp/php-client-a - 禁用任何带前缀的全局路径(如
/tmp/php-client-a),PHP 内部生成的临时文件不保证跨进程唯一性
普通用户依赖 XDG_RUNTIME_DIR + sticky bit
登录用户的临时行为(如脚本运行、CLI 工具缓存)无法靠命名空间硬隔离,但可通过运行时环境和权限策略大幅收窄风险面:
- 确保系统启用
pam_systemd,使每个用户登录后自动获得专属$XDG_RUNTIME_DIR(通常是/run/user/$(id -u)),该路径默认仅属主可读写 - 强制脚本或工具使用该路径,例如:
export TMPDIR="$XDG_RUNTIME_DIR"或命令中显式指定--tmp-dir "$XDG_RUNTIME_DIR" - 加固全局临时目录:
chmod 1777 /tmp(保留 sticky bit 防删他人文件),清理/var/tmp下非必要开放权限的子目录,尤其禁止用户对他人临时目录执行chown或chmod
数据库与应用层联合设防
权限隔离不能只靠文件系统,数据层面也必须同步约束:
- PostgreSQL 可用 RLS(行级安全策略)为每个租户配置访问规则,数据库引擎自动过滤,无需改应用代码
- 共享数据库+独立 Schema 方案中,为每个租户创建专属 schema,并限制其只能访问自身 schema 下的对象
- 应用中间件层拦截 SQL 查询,动态注入
tenant_id = ?条件,作为兜底防护 - 缓存键名统一加租户前缀(如
tenant23:order_list),避免跨租户缓存污染

















