FreeBSD 15.1在 jail 中安装配置 nginx, php-fpm 与 mariadb

我的 exsi 服务器中已经全新安装了 FreeBSD 15.1虚拟机,我准备在这个虚拟机中新建3个 jail 分别安装 nginx、php-fpm 与 mariadb,以实现各自的独立运行环境与资源隔离。

0. 整体网络与 ip 规划
环境/服务 内网固定 ip 外部映射端口 作用
宿主机 (Host) 公网 IP / 内网网关
10.0.0.1
- 路由转发、PF 防火墙控制
web_jail (Nginx) 10.0.0.2 10080 / 10443 负责静态资源、SSL 解密、反向代理
php_jail (PHP-FPM) 10.0.0.3 不对外开放 负责解析 PHP 动态脚本
mariadb_jail (Mariadb) 10.0.0.4 不对外开放 负责数据持久化存储
1. 宿主机网络配置 (/etc/rc.conf)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 开启路由转发功能,允许 jail 访问外网
gateway_enable="YES"

# 创建虚拟网卡 lo1 用于 jail 内网通信
cloned_interfaces="lo1"

# 给 lo1 配置成 jail 的“网关”
ifconfig_lo1_alias="10.0.0.1/24"

# 启用防火墙
pf_enable="YES"
pf_rules="/etc/pf.conf"

# 设置 jail 随宿主机开机自启
jail_enable="YES"
1
2
# 立即启动虚拟网卡
service netif cloneup
2. 完善防火墙策略 (/etc/pf.conf)

编辑宿主机的 /etc/pf.conf,将外部非标端口准确导向 web_jail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 定义接口
ext_if = "vmx0"
int_if = "lo1"

# 2. 定义各个 jail 的静态内网 ip
web_jail = "10.0.0.2"
php_jail = "10.0.0.3"
mariadb_jail = "10.0.0.4"

# 3. 允许 10.0.0.0/24 网段的所有内网流量通过公网网卡进行 NAT 转换出网
nat on $ext_if from 10.0.0.0/24 to any -> ($ext_if)

# 4. 端口映射
rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> $web_jail port 80
rdr on $ext_if proto tcp from any to ($ext_if) port 443 -> $web_jail port 443

# 5. 放行所有常规流量
pass all

1
2
# 立即启动 pf 防火墙
service pf start

3. jail 框架创建与初使化
  • 编写 jail 全局配置文件 (/etc/jail.conf)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    exec.start = "/bin/sh /etc/rc";
    exec.stop = "/bin/sh /etc/rc.shutdown";
    exec.clean;
    mount.devfs;

    web_jail {
    path = "/usr/local/jails/web_jail";
    host.hostname = "web.local";
    interface = "lo1";
    ip4.addr = "10.0.0.2";
    }

    php_jail {
    path = "/usr/local/jails/php_jail";
    host.hostname = "php.local";
    interface = "lo1";
    ip4.addr = "10.0.0.3";
    }

    mariadb_jail {
    path = "/usr/local/jails/mariadb_jail";
    host.hostname = "mariadb.local";
    interface = "lo1";
    ip4.addr = "10.0.0.4";
    }
  • 下载并初始化三个 jail 的目录系统
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 创建 jail 根目录
    mkdir -p /usr/local/jails/web_jail
    mkdir -p /usr/local/jails/php_jail
    mkdir -p /usr/local/jails/mariadb_jail

    # 使用 bsdinstall 自动化下载并释放系统文件到各个 jail (需要联网)
    bsdinstall jail /usr/local/jails/web_jail
    bsdinstall jail /usr/local/jails/php_jail
    bsdinstall jail /usr/local/jails/mariadb_jail
  • 配置 jail 的 DNS 并启动它们
    为了让 jail 内部能够使用 pkg 安装软件,需要将宿主机的 DNS 配置文件复制给它们:
    1
    2
    3
    cp /etc/resolv.conf /usr/local/jails/web_jail/etc/
    cp /etc/resolv.conf /usr/local/jails/php_jail/etc/
    cp /etc/resolv.conf /usr/local/jails/mariadb_jail/etc/
  • 启动 jail
    1
    2
    # 立即启动所有 jail
    service jail start
4. 完善 jail 内部服务配置
  • 在宿主机操作代码目录共享
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 1. 在宿主机创建一个存放网站代码的公共目录
    mkdir -p /home/html

    # 2. 将此目录通过挂载的方式共享给 web 和 php 两个 jail
    mkdir -p /usr/local/jails/web_jail/home/html
    mkdir -p /usr/local/jails/php_jail/home/html

    mount_nullfs /home/html /usr/local/jails/web_jail/home/html
    mount_nullfs /home/html /usr/local/jails/php_jail/home/html

    为了让挂载永久生效,可将 mount_nullfs 写入宿主机的 /etc/fstab 中

  • 配置 php_jail (php-fpm 环境)
    • 进入 PHP 容器:
      1
      jexec php_jail csh
    • 安装 php 及常用扩展,并设置开机自启:
      1
      pkg install php85 php85-bcmath php85-brotli php85-bz2 php85-bzip3 php85-calendar php85-ctype php85-curl php85-dom php85-enchant php85-exif php85-extensions php85-ffi php85-fileinfo php85-filter php85-ftp php85-gd php85-gettext php85-gmp php85-iconv php85-lz4 php85-mbstring php85-mysqli php85-pgsql php85-posix php85-readline php85-session php85-simplexml php85-soap php85-sockets php85-sodium php85-sqlite3 php85-tidy php85-tokenizer php85-xml php85-xmlreader php85-xmlwriter php85-xsl php85-zip php85-zlib php85-zstd
    • 编辑 php-fpm 配置文件 /usr/local/etc/php-fpm.d/www.conf,修改监听模式:
      1
      2
      3
      4
      5
      # 找到 listen 这一行,将其修改为监听自己的内网 ip 和 9000 端口
      listen = 10.0.0.3:9000

      # 找到 listen.allowed_clients,允许 web jail (10.0.0.2) 访问
      listen.allowed_clients = 10.0.0.2
    • 启动 php-fpm 并退出:
      1
      2
      3
      sysrc php_fpm_enable=YES
      service php_fpm start
      exit
  • 配置 web_jail (nginx + ssl 环境)
    • 进入 Web 容器:
      1
      jexec web_jail csh
    • 安装 nginx 并设置自启:
      1
      2
      pkg install nginx
      sysrc nginx_enable=YES
    • 编辑 /usr/local/etc/nginx/nginx.conf,重点是配置 fastcgi_pass 指向 php jail:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      server {
      listen 10.0.0.2:80;
      listen 10.0.0.2:443 ssl;
      server_name yourdomain.com;

      ssl_certificate /root/yourdomain.com/fullchain.cer;
      ssl_certificate_key /root/yourdomain.com/yourdomain.com.key;

      root /home/html;
      index index.php index.html index.htm;

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

      # 关键:将 php 脚本转发给 php_jail
      location ~ \.php$ {
      root /home/html;
      fastcgi_pass 10.0.0.3:9000; # 指向 php 容器的 ip
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
      }
      }
    • 启动 nginx 并退出:
      1
      2
      service nginx start
      exit
  • 配置 mariadb_jail (Mariadb 数据库环境)
    • 进入数据库容器:
      1
      jexec mariadb_jail csh
    • 安装 mariadb 服务器并设置自启动
      1
      2
      pkg install mariadb118-server
      sysrc mysql_enable=YES
    • 编辑 mariadb 配置文件(FreeBSD 路径通常为 /usr/local/etc/mysql/my.cnf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #
      # This group is read both by the client and the server
      # use it for options that affect everything, see
      # https://mariadb.com/kb/en/configuring-mariadb-with-option-files/#option-groups
      #
      [client-server]
      port = 3306
      socket = /var/run/mysql/mysql.sock

      [mysqld]
      max_allowed_packet = 1024M
      character-set-server = utf8mb4
      collation-server = utf8mb4_unicode_ci
      character-set-client-handshake = FALSE
      #
      # include *.cnf from the config directory
      #
      !includedir /usr/local/etc/mysql/conf.d/
    • 编辑 /usr/local/etc/mysql/conf.d/server.cnf,确保其监听内网 ip:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      # Options specific to server applications, see
      # https://mariadb.com/kb/en/configuring-mariadb-with-option-files/#server-option-groups

      # Options specific to all server programs
      [server]

      # Options specific to MariaDB server programs
      [server-mariadb]

      #
      # Options for specific server tools
      #

      [mysqld]
      user = mysql
      # port = 3306 # inherited from /usr/local/etc/mysql/my.cnf
      # socket = /var/run/mysql/mysql.sock # inherited from /usr/local/etc/mysql/my.cnf
      bind-address = 10.0.0.4
      basedir = /usr/local
      # datadir = /var/db/mysql # set with --db_dir from rc-script
      net_retry_count = 16384
      log_error = /var/log/mysql/mysqld.err
      # [mysqld] configuration for ZFS
      # From https://www.percona.com/resources/technical-presentations/zfs-mysql-percona-technical-webinar
      # Create separate datasets for data and logs, eg
      # zroot/mysql compression=on recordsize=128k atime=off
      # zroot/mysql/data recordsize=16k
      # zroot/mysql/logs
      # datadir = /var/db/mysql/data
      # innodb_log_group_home_dir = /var/db/mysql/log
      # audit_log_file = /var/db/mysql/log/audit.log
      # general_log_file = /var/db/mysql/log/general.log
      # log_bin = /var/db/mysql/log/mysql-bin
      # relay_log = /var/db/mysql/log/relay-log
      # slow_query_log_file = /var/db/mysql/log/slow.log
      # innodb_doublewrite = 0
      # innodb_flush_method = O_DSYNC

      # Options read by `mariadb-safe` (and `mysql_safe`)
      [mariadb-safe]

      # Options read my `mariabackup`
      [mariabackup]

      # Options read by mariadb-upgrade (and `mysql_upgrade`)
      [mariadb-upgrade]

      # Specific options read by the mariabackup SST method
      [sst]
    • 启动 mariadb 并进行安全初始化:
      1
      2
      service mysql-server start
      mariadb-secure-installation
    • 进入 mariadb 命令行,授权 php_jail (10.0.0.3) 能够远程连接数据库:
      1
      mariadb
    • 执行以下 sql 语句(创建 root 远程连接,以便使用 phpMyadmin):
      1
      2
      3
      create user 'root'@'10.0.0.3' identified by 'your root password';
      grant all privileges on *.* to 'root'@'10.0.0.3';
      flush privileges;
5. 解决跨容器的权限同步问题
  • 统一 web jail 与 php jail 的 web 用户 UID
    在 FreeBSD nginx 和 php-fpm 默认都使用系统自带的 www 用户和 www 组。最核心的关键:必须确保 web_jail 和 php_jail 内部的 www 用户的 UID(用户ID)和 GID(组ID)完全一致(FreeBSD 默认通常都是 80)。
    你可以分别进入两个 Jail 检查一下:
    1
    2
    jexec web_jail id www
    jexec php_jail id www
    如果输出都显示 uid=80(www) gid=80(www),则说明已经统一,可以直接进入下一步。
  • 在宿主机上修正共享目录的权限
    由于是 php-fpm 负责接收文件并写入磁盘,因此该目录在宿主机层面,必须允许 UID 80 的用户拥有写权限。
    • 将整个网站目录的所属用户和组,更改为 80:80(即两边 jail 对应的 www 权限):
      1
      chown -R 80:80 /home/html
    • 设置合理的目录和文件权限:
      1
      2
      3
      4
      5
      6
      # 网站目录及子目录设置为 755(所有者可读写执行,其他人可读可执行)
      find /home/html -type d -exec chmod 755 {} \;

      # 网站文件设置为 644(所有者可读写,其他人只读)
      find /home/html -type f -exec chmod 644 {} \;

  • 配置 php-fpm 允许上传及限制临时目录
    我们需要进入 php_jail,确保 php 自身的上传功能已打开,并且其产生临时文件的目录也是 www 用户可以读写的。
    • 进入 php_jail:
      1
      jexec php_jail csh
    • 修改 php 配置文件(/usr/local/etc/php.ini)
      1
      2
      3
      4
      5
      6
      file_uploads = On          ; 开启文件上传
      upload_max_filesize = 50M ; 允许上传的最大单文件大小(根据需求调整)
      post_max_size = 50M ; POST 数据的最大容量

      ; 关键:设置一个 Jail 内部 www 用户绝对有权限读写的临时目录
      upload_tmp_dir = "/tmp"
    • 确保 php_jail 内部的 /tmp 目录权限对 www 开放(FreeBSD 默认开放):
      1
      chmod 1777 /tmp
    • 重启 php-fpm 并退出 jail
      1
      2
      service php_fpm restart
      exit
  • 配置 nginx 允许大文件上传
    如果用户上传的文件比较大(例如超过 1MB),nginx 默认会直接拦截并返回 413 Request Entity Too Large 错误。因此还需要调整 web_jail 的 nginx 配置。
    • 进入 web_jail:
      1
      jexec web_jail csh
    • 编辑 /usr/local/etc/nginx/nginx.conf:
      1
      2
      3
      4
      5
      6
      http {
      ...
      # 关键:允许客户端上传的最大主体大小,必须大于或等于 php.ini 里的设定
      client_max_body_size 50m;
      ...
      }
    • 重启 nginx 服务并退出:
      1
      2
      service nginx restart
      exit