解决个人用户目录做为 Web 服务根目录的权限问题

我们在个人帐号目录下进行日常的开发工作,通常使用弱权限帐号(如:nobody)运行 Web 服务器(如:nginx)。

nginx 配置 Web 服务器的根目录为个人用户目录,这样修改代码后刷新浏览器就可以看到效果。

user nobody;

server {
   listen       80;
   server_name  www.example.com;

   location / {
       root   /home/tangxinfa/projects/www.example.com;
       index  index.html index.htm;
   }
}

然而 linux 的用户权限系统禁止 Web 服务器用户(nobody)访问个人用户(tangxinfa)的数据。

使用浏览器访问,会得到一个错误页面

403 Forbidden

使用 linux 命令确认问题是由用户权限系统引起

$ sudo -u nobody -g nobody ls -la /home/tangxinfa/projects/www.example.com
ls: cannot access '/home/tangxinfa/projects/www.example.com': Permission denied

解决方法有以下几种:

  • 使用软链接

    很多人都会下意识地想到通过软链接来解决

    sudo ln -s /home/tangxinfa/projects/www.example.com /var/www/
    sudo chown -R nobody:nobody /var/www/www.example.com
    sudo chown -R nobody:nobody /home/tangxinfa/projects/www.example.com
    

    将个人用户目录链到 Web 服务器用户目录下是没有用的,linux 按原始路径(/home/tangxinfa/projects/www.example.com)来检查权限。

    可以反过来,将项目从个人用户目录移到 Web 服务器用户目录下,然后反向建一个软链接,即可以让 Web 服务器工作正常,又不影响日常开发。

    sudo mv /home/tangxinfa/projects/www.example.com /var/www/
    ln -s /var/www/www.example.com /home/tangxinfa/projects/
    

    但是,如果项目是版本控制系统(如:git)仓库的一个子项目(目录),将目录变成软链接后 git 会认为目录被删除了。

  • 用户目录给 Web 服务帐号开放权限

    按 linux 的帐号权限系统要求,修改(chmod)用户目录的属性,每一级目录的权限都要修改,容易过渡放开权限,引入安全问题。

    设置 Web 服务帐号为用户帐号

    user tangxinfa;
    

    由于 Web 服务器(nginx)通常配有多个服务,Web 服务帐号是全局共用的,其它服务的目录权限也要进行调整。 如果 nginx 只运行这一个服务的话,还是可行的。

    设置 Web 服务帐号为 root 帐号

    user root;
    

    使用 root 帐号会引入安全隐患,一般不推荐,但很少会遇到目录权限方面的问题,可以应急使用。

  • 将用户目录挂载到 Web 帐号目录下

    mount 命令支持将一个目录重新挂载到其它位置

    sudo mkdir /var/www/www.example.com
    sudo chown nobody:nobody /var/www/www.example.com
    sudo mount --bind -o ro,username=nobody /home/tangxinfa/projects/www.example.com /var/www/www.example.com
    

    Web 服务器和日常开发可以兼顾,两全其美的方案。

    bind 形式的挂载放到 /etc/fstab 会挂载失败(估计是挂载时相关依赖还没有准备好),影响开机。

    创建挂载脚本 /usr/sbin/bind-mounts 并添加可执行权限,内容如下

    #!/bin/bash
    
    mount --bind -o ro,username=nobody /home/tangxinfa/projects/www.example.com /var/www/www.example.com
    

    创建 systemd 服务文件 /usr/lib/systemd/system/bind-mounts.service

    [Unit]
    Description=Bind Mounts
    After=local-fs.target
    
    [Service]
    Type=simple
    ExecStart=/usr/sbin/bind-mounts
    
    [Install]
    WantedBy=multi-user.target
    

    启用 bind-mounts 服务

    sudo systemctl enable bind-mounts