多个网站部署在同一台服务器上安全吗?隔离、权限与资源分配指南

一台 4 核 8G 的云服务器上托管了公司官网、客户的几个展示站、还有一个测试环境,成本确实省了。但总有隐隐的不安:万一其中一个站被挂马,会不会连累其他站?某个站点半夜跑批处理把 CPU 占满,会不会让所有站一起打不开?客户的安全扫描报告里“同一 IP 托管多站点”是不是风险项?

答案是:多站点同机本身不是安全问题,缺乏隔离才是。虚拟机、容器、云服务器本质上都是多租户共享硬件,区别在于隔离做得够不够。本文从四个维度展开——Web 层隔离、系统用户与权限隔离、资源配额隔离、数据与网络隔离——每一层都给出可直接落地的配置和明确的判断标准,并在最后给出“什么时候必须拆分成多台机器”的决策依据。

一、先认清同机多站的四类真实风险

风险类型典型场景后果对应隔离手段
跨站入侵(连坐)A 站有漏洞被上传 Webshell,攻击者读取同盘上的 B 站配置文件数据库账号泄露、B 站被篡改独立系统用户 + PHP-FPM 独立池 + open_basedir
资源争抢C 站跑导出脚本占满 CPU/内存所有站点响应变慢甚至 OOM 被杀FPM 进程数配额 + systemd/cgroup 限额
故障放大Nginx 配置一处语法错误本机所有站点同时不可用配置即代码 + 变更回滚机制
外部连坐同 IP 上有违规站点被封、共用 IP 的邮件服务器被拉黑你的站点被连带限流、发信被退独立公网 IP、独立出口 IP

这四类里,前两类是可以通过配置彻底解决的,第三类靠流程治理,第四类只能靠加 IP——下文逐个展开。

二、Web 层隔离:每个站点一个独立配置文件

最基础的一条纪律:不要把所有站点写在一个 nginx.conf 里。用 conf.d/ 拆分,一个站点一个文件,改错只影响自己,排查也方便。

2.1 目录约定

/etc/nginx/nginx.conf                  # 主配置,只放全局参数
/etc/nginx/conf.d/site-a.conf          # 站点 A
/etc/nginx/conf.d/site-b.conf          # 站点 B
/etc/nginx/snippets/php_fastcgi.conf   # 公共 FastCGI 片段

/data/www/site-a/                      # 站点 A 根目录,属主 site-a
/data/www/site-b/                      # 站点 B 根目录,属主 site-b
/var/log/nginx/site-a.access.log
/var/log/nginx/site-a.error.log

2.2 通用 server 块模板

server {
    listen 80;
    server_name www.site-a.com site-a.com;

    root /data/www/site-a;
    index index.php index.html;

    access_log /var/log/nginx/site-a.access.log;
    error_log  /var/log/nginx/site-a.error.log;

    client_max_body_size 20m;      # 按站点需求单独设定,不用全局值

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/run/php/site-a.sock;   # 关键:每个站点独立 socket
        fastcgi_index  index.php;
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_connect_timeout 5s;
        fastcgi_read_timeout    60s;
    }

    location ~ /\. { deny all; }                    # 禁止访问 .git/.env/.svn
    location ~* \.(jpg|jpeg|png|gif|css|js)$ { expires 30d; access_log off; }
}

2.3 防止恶意域名解析(必做)

Nginx 会把没有匹配到 server_name 的请求交给第一个 server 块处理。如果不设 default_server,别人把任意域名解析到你的 IP,就能白嫖你的站点内容(还可能被搜索引擎判为镜像站)。

# 兜底 server:放到 conf.d/00-default.conf,确保文件名排序最前
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;

    # 自签证书或直接拒绝,避免 Nginx 报错
    ssl_certificate     /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;

    return 444;   # 直接断开连接,不返回任何内容
}

三、系统用户隔离:一个站点一个用户

这是防止跨站入侵的核心。原则很简单:站点 A 的进程不能有权限读站点 B 的文件。做法是给每个站点创建独立的系统用户,并让对应的 PHP-FPM 池以该用户身份运行。

3.1 创建站点专用用户

# 创建不可登录的系统用户,家目录即站点根目录
useradd -r -s /sbin/nologin -d /data/www/site-a -M site-a
useradd -r -s /sbin/nologin -d /data/www/site-b -M site-b

# 确认没有 shell 登录权限
grep -E 'site-a|site-b' /etc/passwd
# site-a:x:1001:1001::/data/www/site-a:/sbin/nologin

# 目录属主与权限:750 意味着其他用户完全无法进入
chown -R site-a:site-a /data/www/site-a
find /data/www/site-a -type d -exec chmod 750 {} \;
find /data/www/site-a -type f -exec chmod 640 {} \;

# 需要 Web 读取的资源放宽
chmod -R 755 /data/www/site-a/static

3.2 为每个站点建立独立的 PHP-FPM 池

# /etc/php/8.1/fpm/pool.d/site-a.conf
[site-a]
user  = site-a
group = site-a

listen = /run/php/site-a.sock
listen.owner = nginx
listen.group = nginx
listen.mode  = 0660

pm = dynamic
pm.max_children         = 20        # 按配额分配,见第五节
pm.start_servers        = 5
pm.min_spare_servers    = 3
pm.max_spare_servers    = 8
pm.max_requests         = 500
request_terminate_timeout = 30s
request_slowlog_timeout   = 3s
slowlog = /var/log/php/site-a.slow.log

# 关键:限制该池只能访问自己的目录
php_admin_value[open_basedir] = /data/www/site-a/:/tmp/:/var/tmp/
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,putenv
php_admin_flag[allow_url_fopen] = off

# 若存在第二个池,注意 pm.status_path 要不同
pm.status_path = /fpm-status-site-a
# 校验配置并重载
php-fpm8.1 -t
systemctl reload php8.1-fpm

# 验证 socket 归属与权限
ls -l /run/php/site-a.sock
# srw-rw---- 1 nginx nginx ...

# 验证 PHP 进程的运行用户确实是 site-a
ps aux | grep 'php-fpm: pool site-a' | grep -v grep | awk '{print $1}' | sort -u

3.3 常见权限模型对比

模型做法安全性适用场景
全部 www-data 单用户所有站点共用一个用户低:一个站沦陷全部沦陷不推荐,仅限个人测试
独立用户 + 独立 FPM 池本文推荐方案高:进程级别隔离多站点托管标配
容器隔离每个站点一个容器高:文件系统 + 进程 + 网络均可隔离需要更强的资源/版本隔离
虚拟机隔离每台虚拟机一个站最高:内核级隔离合规要求严格、互相不信任的多方业务

3.4 Nginx FastCGI 侧的配套防护

# 防止 PHP 文件解析漏洞(如 /upload/a.jpg/a.php)
location ~ \.php$ {
    try_files $uri =404;                       # 文件不存在直接 404,不让 FPM 猜路径
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/run/php/site-a.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# php.ini 里同时关闭 pathinfo 修正
cgi.fix_pathinfo = 0

# 上传目录禁止执行 PHP
location ^~ /upload/ {
    location ~ \.php$ { deny all; }
}

四、资源分配:避免一个站拖垮全部

权限隔离管的是安全,资源限额管的是可用性。两者都要做。

4.1 按内存配额反推 FPM 进程数

# 计算总预算:假设 8G 内存机器,托管 3 个站,预留 2G 给系统+MySQL+Nginx
可用给 PHP 的内存 ≈ 4GB

# 分配方案(按站点重要性)
站点 A(核心): 2GB  → 单进程 40MB → pm.max_children = 2048/40 ≈ 50
站点 B(一般): 1.5GB → 单进程 40MB → pm.max_children ≈ 38
站点 C(测试): 0.5GB → 单进程 40MB → pm.max_children ≈ 12

把算出来的值分别写进三个 FPM 池。这样即使站点 C 跑死,它也最多占用自己那份进程数,不会抢走 A 和 B 的资源。

4.2 用 systemd 做更硬的限额

如果想连 CPU 也限制住,可以把大流量/不可信的站点放到独立的 PHP-FPM 服务实例,再用 systemd 配额:

# 复制一份独立 FPM 服务(Ubuntu 下 php8.1-fpm 支持多实例)
cp /lib/systemd/system/php8.1-fpm.service /etc/systemd/system/php-fpm-sitec.service
systemctl daemon-reload
systemctl enable --now php-fpm-sitec

# 设置 CPU 与内存上限
systemctl set-property php-fpm-sitec.service CPUQuota=50% MemoryMax=1G

# 持久化写入 drop-in 文件 /etc/systemd/system.control/php-fpm-sitec.d/*.conf
systemctl show php-fpm-sitec | grep -E 'CPUQuota|MemoryMax'

CPUQuota=50% 表示最多使用一个核的 50%(200% 即两个核)。MemoryMax 达到上限后该服务会被限流或杀进程,但不会波及其他服务。

4.3 Nginx 层的限流与连接控制

# http 段定义
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_req_zone  $binary_remote_addr zone=siteb:10m rate=10r/s;

# 站点 B 的 server/location 里应用
server {
    server_name www.site-b.com;
    limit_conn perip 20;              # 单 IP 最多 20 个并发连接
    location / {
        limit_req zone=siteb burst=20 nodelay;   # 单 IP 10 req/s,突发 20
        try_files $uri $uri/ /index.php?$query_string;
    }
}

4.4 磁盘与 inode 配额

# 查看某站点占用
du -sh /data/www/site-a
df -i                                 # inode 耗尽同样会让站点无法写入

# XFS 项目配额(CentOS/RHEL)
xfs_quota -x -c 'project -s -p /data/www/site-a 100' /data
xfs_quota -x -c 'limit -p bsoft=18g bhard=20g 100' /data
xfs_quota -x -c 'report -h' /data

五、数据与网络层面的隔离

5.1 数据库必须一库一账号

# 每个站点独立库 + 独立账号,只授权自己的库
CREATE DATABASE site_a DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'sa'@'localhost' IDENTIFIED BY '强密码A';
GRANT ALL PRIVILEGES ON site_a.* TO 'sa'@'localhost';
FLUSH PRIVILEGES;

# 绝不使用 root 连接业务库
mysql -e "SELECT user,host FROM mysql.user;"
mysql -e "SHOW GRANTS FOR 'sa'@'localhost';"

# 确认 MySQL 只监听本地,不被外部直连
grep -nE 'bind-address|skip-networking' /etc/my.cnf
ss -lntp | grep 3306
# 正确结果应为 127.0.0.1:3306 或仅 socket

5.2 Redis 也要隔离

  • 不要让多个站点共用一个 Redis 库且无密码:设置 requirepass,或按业务分配不同 db index(SELECT 0/1/2),但更推荐实例分离。
  • 给不同站点用不同的 key 前缀,避免互相覆盖。
  • Redis 禁止监听 0.0.0.0,加 rename-command FLUSHALL "" 等危险命令重命名。

5.3 公网 IP 与邮件出口

关注点同 IP 影响建议
搜索引擎一般无影响,搜索引擎按域名而非 IP 收录正常托管即可
HTTPSSNI 已普及,多证书共存无问题确保每站配置正确的 server_name 与证书
HTTP/2同一连接可复用,多站共享无影响无需处理
邮件发送同 IP 有人发垃圾邮件会导致你的退信率飙升使用第三方 SMTP 中继(独立发信域名);业务邮件不要自建
合规审查等保/客户尽调可能要求业务隔离合规场景建议拆机或用独立 VPC

六、什么时候必须拆分成多台服务器

给出明确的判断标准,避免凭感觉决策:

  1. 安全等级不同:涉及支付、用户隐私数据的业务,不要和展示型站点、测试环境混部。
  2. 资源已无余量:高峰期 load 长期高于核数 × 2,或可用内存长期低于 20%,加更多站点只会互相拖累。
  3. 软件版本冲突:一个站要 PHP 5.6、另一个要 PHP 8.1,同机维护成本极高,应当拆开或用容器。
  4. 合规要求:客户合同或等级保护测评明确要求物理/逻辑隔离。
  5. 故障影响面不可接受:核心业务停机 1 分钟损失巨大,就不该与任何第三方站点共享机器。
  6. SLA 要求不同:核心站要求 99.9%,测试站随便重启,两者混部必然妥协。

七、多站点部署实施清单

  1. 规划目录与命名:/data/www/<站点>,一个站点一个 conf.d 配置文件。
  2. 为每个站点创建独立不可登录用户,主目录权限 750。
  3. 为每个站点建立独立 PHP-FPM 池,池用户即站点用户,socket 独立命名。
  4. 配置 open_basedir 限制文件访问范围,配置 disable_functions 禁用危险函数。
  5. Nginx server 块加 try_files $uri =404,上传目录禁止 PHP 执行。
  6. 设置 default_server 返回 444,防止恶意域名解析。
  7. 关闭 PHP cgi.fix_pathinfo。
  8. 为每个站点创建独立数据库和独立账号,禁止使用 root 连接。
  9. 按内存预算反推每个池的 pm.max_children,总和不超载。
  10. 为资源敏感站点配置 systemd CPUQuota / MemoryMax 或容器限额。
  11. 每站点独立 access_log / error_log,配置 logrotate 轮转。
  12. 每站点独立备份任务与保留策略,定期验证可恢复。
  13. 配置 fail2ban、SSH 禁密码登录、仅开放必要端口。
  14. 建立变更流程:改配置先 nginx -t,保留回滚版本。

小结

多台网站部署在同一台服务器上完全可以做到安全可控,前提是完成四层隔离:用户层(独立系统用户 + 独立 FPM 池)、文件层(open_basedir + 目录权限)、资源层(进程数配额 + systemd/cgroup 限额)、数据层(独立库账号 + Redis 隔离)。做完这四层,单个站点被入侵或跑飞,影响范围就被限制在自己那一亩三分地内。

避坑清单:

  • 永远不要让所有站点共用一个 Web 用户,这是最致命也最常见的错误。
  • 不要给任何目录 777,也不要让站点 A 的用户能读站点 B 的配置文件。
  • 别忘设 default_server,否则会被恶意解析白嫖流量。
  • pm.max_children 必须按“站点配额”分配而不是所有池都用同一个大数字,否则内存照样会爆。
  • 多个池的 slowlog、socket、pm.status_path 都不能重名,否则会互相覆盖。
  • 测试环境和生产绝不同机,很多事故都源于“临时借用生产机跑个测试”。
  • 业务邮件走第三方中继,别让同 IP 的其他站影响你的发信信誉。
  • 备份必须逐站点独立配置,共用备份脚本时务必验证每个站点都能单独恢复。

关键词回顾:多网站部署同一服务器、站点隔离、PHP-FPM 独立池、open_basedir、独立系统用户、资源配额、独立数据库账号、安全配置。