nginx

来自 ArchWiki

nginx (发音为 "engine X"),是由 Igor Sysoev 于 2005 年编写的免费、开源、高性能 HTTP web 服务器和反向代理服务器,以及 IMAP/POP3 代理服务器。nginx 以其稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。

本文介绍如何设置 nginx 以及如何选择性地通过 #FastCGIPHP 集成。

安装

安装以下软件包之一

  • nginx-mainline - 主线分支:新功能、更新、错误修复。
  • nginx - 稳定分支:仅重大错误修复。
  • angieAUR - nginx 的分支和替代品,具有更多功能。
  • freenginx-mainlineAUR - 保留 nginx 免费和开放开发的替代品(主线分支)。
  • freenginx-libresslAUR - 保留 nginx 免费和开放开发的替代品(带有 LibreSSL 支持的主线分支)。
  • freenginxAUR - 保留 nginx 免费和开放开发的替代品(稳定分支)。

建议使用主线分支。使用稳定分支的主要原因是您担心新功能可能带来的影响,例如与第三方模块不兼容或在新功能中无意引入错误。

注意: 官方仓库中提供的所有 nginx 模块都需要 nginx 软件包(而不是 nginx-mainline)作为依赖项。在做出 nginxnginx-mainline 的决定之前,最好查看您可能需要/想要的模块列表。nginx-mainline 的模块可以在 Arch 用户仓库中找到。

对于基于 chroot 的安装以获得额外的安全性,请参阅 #在 chroot 中安装

运行

启动/启用 nginx.serviceangie.service(如果您使用 Angie)。

http://127.0.0.1 提供的默认页面是 /usr/share/nginx/html/index.html

配置

nginx 的入门步骤在初学者指南中描述。您可以通过编辑 /etc/nginx/ 中的文件来修改配置。主配置文件位于 /etc/nginx/nginx.conf

更多详细信息和示例可以在官方文档中找到。

以下示例涵盖了最常见的用例。假设您使用默认的文档位置 (/usr/share/nginx/html)。如果不是这种情况,请替换为您的路径。

提示: DigitalOcean 提供了一个 Nginx 配置工具

配置示例

/etc/nginx/nginx.conf
user http;
worker_processes auto;
worker_cpu_affinity auto;

events {
    worker_connections 1024;
}

http {
    charset utf-8;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    log_not_found off;
    types_hash_max_size 4096;
    client_max_body_size 16M;

    # MIME
    include mime.types;
    default_type application/octet-stream;

    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    # load configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

常规配置

进程和连接

您应该为 worker_processes 选择一个合适的值。此设置最终定义了 nginx 将接受多少连接以及它将能够利用多少处理器。通常,将其设置为系统中硬件线程的数量是一个好的开始。或者,worker_processes 自版本 1.3.8 和 1.2.5 以来接受 auto 值,这将尝试自动检测最佳值(来源)。

nginx 将接受的最大连接数由 max_clients = worker_processes * worker_connections 给出。

以其他用户身份运行

默认情况下,nginxroot 身份运行主进程,并以用户 http 身份运行工作进程。要以其他用户身份运行工作进程,请更改 nginx.conf 中的 user 指令

/etc/nginx/nginx.conf
user user [group];

如果省略组,则使用名称与 user 相同的组。

提示: 也可以使用 systemd 在不以 root 身份运行任何内容的情况下运行 nginx。请参阅 #使用 systemd 以非特权用户运行#使用 systemd 运行用户服务

服务器块

可以使用 server 块来服务多个域。这些与 Apache HTTP 服务器中的“VirtualHosts”相当。另请参阅上游示例

在下面的示例中,服务器侦听两个域 domainname1.tlddomainname2.tld 的 IPv4 和 IPv6 端口 80 上的传入连接

/etc/nginx/nginx.conf
...
server {
    listen 80;
    listen [::]:80;
    server_name domainname1.tld;
    root /usr/share/nginx/domainname1.tld/html;
    location / {
        index index.php index.html index.htm;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name domainname2.tld;
    root /usr/share/nginx/domainname2.tld/html;
    ...
}

重启 nginx.service 以应用任何更改。

注意: 确保主机名可通过设置 DNS 服务器(如 BINDdnsmasq)解析,或查看 网络配置#本地网络主机名解析
管理服务器条目

可以将不同的 server 块放在不同的文件中。这允许您轻松地启用或禁用某些站点。

本文或本节的事实准确性存在争议。

原因: 使用 sites-enabledsites-available 的以下方法是否仍然有用并且不会产生更多问题存在争议,请参阅比较这两种方法通过 sites-enabled 和 sites-available 方法出现问题的示例

相反,可以只在 etc/nginx/conf.d/ 中创建文件,这符合放置配置文件的标准。然后,在主配置文件中包含 include /etc/nginx/conf.d/*.conf,类似于包含其他目录中的其他文件模式,如下所示。这样,只需将站点重命名为例如 original_name.conf.disabled 即可禁用站点,因为只有以 .conf 结尾的文件才会被包含。

(在Talk:Nginx中讨论)

对于使用 sites-enabledsites-available 方法,创建以下目录

# mkdir /etc/nginx/sites-available
# mkdir /etc/nginx/sites-enabled

sites-available 目录中创建一个文件,其中包含一个或多个服务器块

/etc/nginx/sites-available/example.conf
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;

    ...
}

include sites-enabled/*; 附加到 http 块的末尾

/etc/nginx/nginx.conf
http {
    ...
    include sites-enabled/*;
}

要启用站点,只需创建一个符号链接

# ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf

要禁用站点,请取消链接活动的符号链接

# unlink /etc/nginx/sites-enabled/example.conf

重载/重启 nginx.service 以启用对站点配置的更改。

TLS

本文或本节需要语言、wiki 语法或样式改进。请参阅Help:Style 以获取参考。

原因: 不要重复 OpenSSL#用法。(在Talk:Nginx中讨论)

OpenSSL 提供 TLS 支持,并在 Arch 安装上默认安装。

提示
  • 您可能需要在配置 SSL 之前先阅读 ngx_http_ssl_module 文档。
  • Let’s Encrypt 是一个免费、自动化和开放的证书颁发机构。有一个插件可用于直接从命令行请求有效的 SSL 证书和自动配置。
  • Mozilla 还有一个有用的 TLS 文章以及一个自动化工具,可帮助创建更安全的配置。
警告: 如果您计划实施 TLS,请注意某些变体和实现仍然容易受到攻击[1]。有关 TLS 中当前漏洞的详细信息以及如何对 nginx 应用适当的更改,请访问 https://weakdh.org/sysadmin.html

创建一个私钥和自签名证书。这对于大多数不需要 CSR 的安装来说已经足够了

# mkdir /etc/nginx/ssl
# cd /etc/nginx/ssl
# openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 1095
# chmod 400 server.key
# chmod 444 server.crt
注意: -days 开关是可选的,RSA 密钥大小可以低至 2048(默认值)。

如果您需要创建 CSR,请按照以下说明而不是上述说明进行操作

# mkdir /etc/nginx/ssl
# cd /etc/nginx/ssl
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out server.key
# chmod 400 server.key
# openssl req -new -sha256 -key server.key -out server.csr
# openssl x509 -req -days 1095 -in server.csr -signkey server.key -out server.crt
注意: 有关更多 openssl 选项,请阅读其手册页 openssl(1ssl) 或仔细阅读其广泛的文档

带有 TLS 的 /etc/nginx/nginx.conf 的起点是 Mozilla 的 SSL 配置生成器

重启 nginx.service 以应用任何更改。

每用户目录

要复制 Apache 样式的 ~user URL 到用户的 ~/public_html 目录,请尝试以下操作。(注意:如果同时使用以下两个规则,则更具体的 PHP 规则必须放在前面。)

/etc/nginx/nginx.conf
...
server {
    ...
    # PHP in user directories, e.g. http://example.com/~user/test.php
    location ~ ^/~(.+?)(/.+\.php)$ {
        alias          /home/$1/public_html$2;
        fastcgi_pass   unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }

    # User directories, e.g. http://example.com/~user/
    location ~ ^/~(.+?)(/.*)?$ {
        alias     /home/$1/public_html$2;
        index     index.html index.htm;
        autoindex on;
    }
    ...
}
...

有关 nginx 的 PHP 配置的更多信息,请参阅 #PHP 实现

重启 nginx.service 以启用新配置。

FastCGI

FastCGI,也称为 FCGI,是一种用于将交互式程序与 Web 服务器连接的协议。FastCGI 是早期 通用网关接口 (CGI) 的变体;FastCGI 的主要目标是减少与连接 Web 服务器和 CGI 程序相关的开销,从而使服务器能够一次处理更多网页请求。

FastCGI 技术被引入 nginx 中,以与许多外部工具(例如 PerlPHPPython)一起工作。

PHP 实现

PHP-FPM 是作为 PHP 的 FastCGI 服务器运行的推荐解决方案。

安装 php-fpm 并确保 PHP 已正确安装和配置。PHP-FPM 的主配置文件是 /etc/php/php-fpm.conf。对于基本用法,默认配置应该足够了。

最后,启动/启用 php-fpm.service

您也可以使用 php-legacy-fpm 代替,请参阅 #使用 php-legacy

注意
  • 如果您以其他用户身份运行 nginx,请确保 PHP-FPM 套接字文件可由此用户访问,或使用 TCP 套接字。
  • 如果您在 chroot 环境中运行 nginx(chroot 是 /srv/nginx-jail,网页在 /srv/nginx-jail/www 中提供),则必须修改文件 /etc/php/php-fpm.conf 以在池部分(默认的是 [www])中包含 chroot = /srv/nginx-jaillisten = /srv/nginx-jail/run/php-fpm/php-fpm.sock 指令。如果缺少套接字文件的目录,请创建它。此外,对于动态链接到依赖项的模块,您需要将这些依赖项复制到 chroot(例如,对于 php-imagick,您需要将 ImageMagick 库复制到 chroot,但不需要复制 imagick.so 本身)。
nginx 配置

当提供 PHP Web 应用程序时,应在每个 服务器块 [2] 中包含 PHP-FPM 的 location,例如

/etc/nginx/sites-available/example.conf
server {
    root /usr/share/nginx/html;

    location / {
        index index.html index.htm index.php;
    }

    location ~ \.php$ {
        # 404
        try_files $fastcgi_script_name =404;

        # default fastcgi_params
        include fastcgi_params;

        # fastcgi settings
        fastcgi_pass			unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index			index.php;
        fastcgi_buffers			8 16k;
        fastcgi_buffer_size		32k;

        # fastcgi params
        fastcgi_param DOCUMENT_ROOT	$realpath_root;
        fastcgi_param SCRIPT_FILENAME	$realpath_root$fastcgi_script_name;
        #fastcgi_param PHP_ADMIN_VALUE	"open_basedir=$base/:/usr/lib/php/:/tmp/";
    }
}

如果需要使用 PHP 处理其他扩展名(例如 .html.htm

location ~ [^/]\.(php|html|htm)(/|$) {
    ...
}

还应在 /etc/php/php-fpm.d/www.conf 中显式添加 PHP-FPM 中非 .php 扩展名的处理

security.limit_extensions = .php .html .htm
注意: 注意 fastcgi_pass 参数,因为它必须是 FastCGI 服务器在其配置文件中定义的 TCP 或 Unix 套接字。php-fpm默认(Unix)套接字是
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;

您可以使用常见的 TCP 套接字,非默认

fastcgi_pass 127.0.0.1:9000;
但是,Unix 域套接字应该更快。
提示: 为了允许多个 server 块使用相同的 PHP-FPM 配置,可以使用 php_fastcgi.conf 配置文件来简化管理
/etc/nginx/php_fastcgi.conf
location ~ \.php$ {
    # 404
    try_files $fastcgi_script_name =404;

    # default fastcgi_params
    include fastcgi_params;

    # fastcgi settings
    ...
}

要为特定服务器启用 PHP 支持,只需包含 php_fastcgi.conf 配置文件

/etc/nginx/sites-available/example.conf
server {
    server_name example.com;
    ...

    include /etc/nginx/php_fastcgi.conf;
}
测试配置

如果配置已更改,您需要重启 php-fpm.servicenginx.service 单元以应用更改。

要测试 FastCGI 实现,请在 root 文件夹中创建一个新的 PHP 文件,其中包含

<?php phpinfo(); ?>

在浏览器中导航到此文件,您应该会看到包含当前 PHP 配置的信息页面。

CGI 实现

CGI 应用程序需要此实现。

fcgiwrap

安装 fcgiwrap。配置通过编辑 fcgiwrap.socket 完成。启用启动 fcgiwrap.socket

多工作线程

如果您想生成多个工作线程,建议您使用 multiwatchAUR,它将负责重启崩溃的子进程。您需要使用 spawn-fcgi 来创建 Unix 套接字,因为 multiwatch 似乎无法处理 systemd 创建的套接字,即使 fcgiwrap 本身在单元文件中直接调用时也没有任何问题。

覆盖单元 fcgiwrap.service(以及 fcgiwrap.socket 单元,如果存在),并修改 ExecStart 行以满足您的需求。这是一个使用 multiwatchAUR 的单元文件。确保未启动或启用 fcgiwrap.socket,因为它会与此单元冲突

/etc/systemd/system/fcgiwrap.service
[Unit]
Description=Simple CGI Server
After=nss-user-lookup.target

[Service]
ExecStartPre=/bin/rm -f /run/fcgiwrap.socket
ExecStart=/usr/bin/spawn-fcgi -u http -g http -s /run/fcgiwrap.sock -n -- /usr/bin/multiwatch -f 10 -- /usr/sbin/fcgiwrap
ExecStartPost=/usr/bin/chmod 660 /run/fcgiwrap.sock
PrivateTmp=true
Restart=on-failure

[Install]
WantedBy=multi-user.target

调整 -f 10 以更改生成的子进程数。

警告: 由于我在为 spawn-fcgi 使用 -M 660 选项时看到的奇怪行为,ExecStartPost 行是必需的。设置了错误的模式。这可能是个错误?
nginx 配置

/etc/nginx 中,将文件 fastcgi_params 复制到 fcgiwrap_params。在 fcgiwrap_params 中,注释或删除设置 SCRIPT_NAMEDOCUMENT_ROOT 的行。

在每个提供 CGI Web 应用程序的 server 块内,应出现类似于以下的 location

location ~ \.cgi$ {
     include       fcgiwrap_params;
     fastcgi_param DOCUMENT_ROOT /srv/www/cgi-bin/;
     fastcgi_param SCRIPT_NAME   myscript.cgi;
     fastcgi_pass  unix:/run/fcgiwrap.sock;
}

fcgiwrap 的默认套接字文件是 /run/fcgiwrap.sock

使用 fastcgi_param SCRIPT_FILENAME /srv/www/cgi-bin/myscript.cgi 是设置 DOCUMENT_ROOTSCRIPT_NAME 的快捷替代方法。如果您使用 SCRIPT_FILENAME,您也不需要将 fastcgi_params 复制到 fcgiwrap_params 并注释掉 DOCUMENT_ROOTSCRIPT_NAME 行。

警告: 如果使用 SCRIPT_NAME 和 DOCUMENT_ROOT,fcgiwrap 将丢弃 nginx 中设置的任何其他 fastcgi_params。您必须使用 SCRIPT_FILENAME,以便可以通过 Nginx 配置设置其他参数(如 PATH_INFO)。请参阅 GitHub 问题。

如果您一直收到 502 - bad Gateway 错误,您应该检查您的 CGI 应用程序是否首先声明了以下内容的 mime 类型。对于 HTML,这需要是 Content-type: text/html

如果您收到 403 错误,请确保 http 用户可以读取和执行 CGI 可执行文件,并且每个父文件夹都可以被 http 用户读取。

在 chroot 中安装

本文或本节的事实准确性存在争议。

原因: 本节来自 2013 年。systemd 此后已被引入,可以代替使用,效率更高,并且没有太多麻烦。(在Talk:Nginx中讨论)

chroot 中安装 nginx 增加了额外的安全层。为了获得最大的安全性,chroot 应仅包含运行 nginx 服务器所需的文件,并且所有文件都应具有尽可能严格的权限,例如,尽可能多地归 root 所有,诸如 /usr/bin 之类的目录应不可读且不可写等。

Arch 默认带有 http 用户和组,它们将运行服务器。chroot 将在 /srv/http 中。

jail.pl gist 中提供了一个 PERL 脚本来创建此 jail。您可以使用该脚本,也可以按照本文中的说明进行操作。它期望以 root 身份运行。您需要取消注释一行,然后它才会进行任何更改。

创建必要的设备

nginx 需要 /dev/null/dev/random/dev/urandom。要在 chroot 中安装这些设备,请创建 /dev/ 目录并使用 mknod 添加设备。避免挂载所有 /dev/,以确保即使 chroot 被破坏,攻击者也必须突破 chroot 才能访问重要的设备(如 /dev/sda1)。

提示
  • 确保 /srv/http 在没有 nodev 选项的情况下挂载
  • 请参阅 mknod(1)ls -l /dev/{null,random,urandom} 以更好地理解 mknod 选项。
# export JAIL=/srv/http
# mkdir $JAIL/dev
# mknod -m 0666 $JAIL/dev/null c 1 3
# mknod -m 0666 $JAIL/dev/random c 1 8
# mknod -m 0444 $JAIL/dev/urandom c 1 9

创建必要的目录

nginx 需要大量文件才能正常运行。在复制它们之前,请创建文件夹来存储它们。这假设您的 nginx 文档根目录将是 /srv/http/www

# mkdir -p $JAIL/etc/nginx/logs
# mkdir -p $JAIL/usr/{lib,bin}
# mkdir -p $JAIL/usr/share/nginx
# mkdir -p $JAIL/var/{log,lib}/nginx
# mkdir -p $JAIL/www/cgi-bin
# mkdir -p $JAIL/{run,tmp}
# cd $JAIL; ln -s usr/lib lib
# cd $JAIL; ln -s usr/lib lib64
# cd $JAIL/usr; ln -s lib lib64

然后将 $JAIL/tmp$JAIL/run 挂载为 tmpfs。大小应受到限制,以确保攻击者无法耗尽所有 RAM。

# mount -t tmpfs none $JAIL/run -o 'noexec,size=1M'
# mount -t tmpfs none $JAIL/tmp -o 'noexec,size=100M'

为了在重启后保持挂载,应将以下条目添加到 /etc/fstab

/etc/fstab
tmpfs   /srv/http/run   tmpfs   rw,noexec,relatime,size=1024k   0       0
tmpfs   /srv/http/tmp   tmpfs   rw,noexec,relatime,size=102400k 0       0

填充 chroot

首先复制容易的文件。

# cp -r /usr/share/nginx/* $JAIL/usr/share/nginx
# cp -r /usr/share/nginx/html/* $JAIL/www
# cp /usr/bin/nginx $JAIL/usr/bin/
# cp -r /var/lib/nginx $JAIL/var/lib/nginx

现在复制所需的库。使用 ldd 列出它们,然后将它们全部复制到正确的位置。复制优于硬链接,以确保即使攻击者获得对文件的写入权限,他们也无法破坏或更改真正的系统文件。

$ ldd /usr/bin/nginx
linux-vdso.so.1 (0x00007fffc41fe000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f57ec3e8000)
libcrypt.so.1 => /usr/lib/libcrypt.so.1 (0x00007f57ec1b1000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f57ebead000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f57ebbaf000)
libpcre.so.1 => /usr/lib/libpcre.so.1 (0x00007f57eb94c000)
libssl.so.1.0.0 => /usr/lib/libssl.so.1.0.0 (0x00007f57eb6e0000)
libcrypto.so.1.0.0 => /usr/lib/libcrypto.so.1.0.0 (0x00007f57eb2d6000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f57eb0d2000)
libz.so.1 => /usr/lib/libz.so.1 (0x00007f57eaebc000)
libGeoIP.so.1 => /usr/lib/libGeoIP.so.1 (0x00007f57eac8d000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f57eaa77000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f57ea6ca000)
/lib64/ld-linux-x86-64.so.2 (0x00007f57ec604000)

对于位于 /usr/lib 中的文件,您可以尝试以下单行命令

# cp $(ldd /usr/bin/nginx | grep /usr/lib/ | sed -sre 's/(.+)(\/usr\/lib\/\S+).+/\2/g') $JAIL/usr/lib

以下命令用于 ld-linux-x86-64.so

# cp /lib64/ld-linux-x86-64.so.2 $JAIL/lib
注意: 不要尝试复制 linux-vdso.so:它不是真正的库,并且不存在于 /usr/lib 中。

复制一些其他必要的库和系统文件。

# cp /usr/lib/libnss_* $JAIL/usr/lib
# cp -rfvL /etc/{services,localtime,nsswitch.conf,nscd.conf,protocols,hosts,ld.so.cache,ld.so.conf,resolv.conf,host.conf,nginx} $JAIL/etc

为 chroot 创建受限的用户/组文件。这样,就 chroot 所知,只有 chroot 功能所需的用户存在,并且如果攻击者获得对 chroot 的访问权限,则不会泄漏任何系统用户/组。

$JAIL/etc/group
http:x:33:
nobody:x:99:
$JAIL/etc/passwd
http:x:33:33:http:/:/bin/false
nobody:x:99:99:nobody:/:/bin/false
$JAIL/etc/shadow
http:x:14871::::::
nobody:x:14871::::::
$JAIL/etc/gshadow
http:::
nobody:::
# touch $JAIL/etc/shells
# touch $JAIL/run/nginx.pid

最后,设置非常严格的权限。尽可能多地归 root 所有并设置为不可写。

# chown -R root:root $JAIL/

# chown -R http:http $JAIL/www
# chown -R http:http $JAIL/etc/nginx
# chown -R http:http $JAIL/var/{log,lib}/nginx
# chown http:http $JAIL/run/nginx.pid

# find $JAIL/ -gid 0 -uid 0 -type d -print | xargs chmod -rw
# find $JAIL/ -gid 0 -uid 0 -type d -print | xargs chmod +x
# find $JAIL/etc -gid 0 -uid 0 -type f -print | xargs chmod -x
# find $JAIL/usr/bin -type f -print | xargs chmod ug+rx
# find $JAIL/ -group http -user http -print | xargs chmod o-rwx
# chmod +rw $JAIL/tmp
# chmod +rw $JAIL/run

如果您的服务器将绑定端口 80(或范围 [1-1023] 中的任何其他端口),请授予 chroot 可执行文件在没有 root 权限的情况下绑定这些端口的权限。

# setcap 'cap_net_bind_service=+ep' $JAIL/usr/bin/nginx

修改 nginx.service 以启动 chroot

覆盖单元 nginx.service。升级 nginx 不会修改您的自定义 .service 文件。

必须更改 systemd 单元以在 chroot 中以 http 用户身份启动 nginx,并将 PID 文件存储在 chroot 中。

注意: 我不确定是否需要将 pid 文件存储在 chroot jail 中。
/etc/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/srv/http/run/nginx.pid
ExecStartPre=/usr/bin/chroot --userspec=http:http /srv/http /usr/bin/nginx -t -q -g 'pid /run/nginx.pid; daemon on; master_process on;'
ExecStart=/usr/bin/chroot --userspec=http:http /srv/http /usr/bin/nginx -g 'pid /run/nginx.pid; daemon on; master_process on;'
ExecReload=/usr/bin/chroot --userspec=http:http /srv/http /usr/bin/nginx -g 'pid /run/nginx.pid; daemon on; master_process on;' -s reload
ExecStop=/usr/bin/chroot --userspec=http:http /srv/http /usr/bin/nginx -g 'pid /run/nginx.pid;' -s quit

[Install]
WantedBy=multi-user.target
注意: 使用 pacman 升级 nginx 不会升级 chrooted nginx 安装。您必须手动重复上述某些步骤来处理更新。不要忘记同时更新它链接到的库。

您现在可以安全地删除非 chrooted nginx 安装。

# pacman -Rsc nginx

如果您不删除非 chrooted nginx 安装,您可能需要确保正在运行的 nginx 进程实际上是 chrooted 进程。您可以通过检查 /proc/PID/root 符号链接到哪里来做到这一点。它应该链接到 /srv/http 而不是 /

# ps -C nginx | awk '{print $1}' | sed 1d | while read -r PID; do ls -l /proc/$PID/root; done

技巧和窍门

使用 systemd 以非特权用户运行

nginx.service 使用放置式单元文件,并在 [Service] 下设置 User 和可选的 Group 选项

/etc/systemd/system/nginx.service.d/user.conf
[Service]
User=user
Group=group

我们可以加强服务以防止权限提升

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
NoNewPrivileges=yes
提示: 有关更多限制选项,请参阅 systemd.exec(5)

然后我们需要确保 user 有权访问它需要的一切。按照以下子节操作,然后启动 nginx。

提示: 相同的设置对于您的 FastCGI 服务器 也可能是理想的。

端口

默认情况下,Linux 不允许非 root 进程绑定到 1024 以下的端口。可以使用 1024 以上的端口

/etc/nginx/nginx.conf
server {
        listen 8080;
}
提示: 如果您希望 nginx 可在端口 80 或 443 上访问,请配置您的 防火墙 以将来自 80 或 443 的请求重定向到 nginx 侦听的端口。

或者,您可以授予 nginx 进程 CAP_NET_BIND_SERVICE 功能,使其能够绑定到 1024 以下的端口

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
CapabilityBoundingSet=
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=
AmbientCapabilities=CAP_NET_BIND_SERVICE

或者,您可以使用 systemd 套接字激活。在这种情况下,systemd 将侦听端口,并在建立连接时,生成 nginx 并将套接字作为文件描述符传递。这意味着 nginx 不需要特殊功能,因为套接字在启动时已经存在。这依赖于 nginx 用于传递套接字的内部环境变量[3],因此未获得官方支持。无需设置 CapabilityBoundingSetAmbientCapabilities,编辑服务覆盖以设置 NGINX 环境变量,以告知 nginx 套接字将作为哪些文件描述符传递

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
Environment=NGINX=3:4;

每个侦听端口将有一个套接字,从文件描述符 3 开始,因此在本例中,我们告诉 nginx 期望有两个套接字。现在创建一个 nginx.socket 单元,指定要侦听的端口

/etc/systemd/system/nginx.socket
[Socket]
ListenStream=0.0.0.0:80
ListenStream=0.0.0.0:443
After=network.target
Requires=network.target

[Install]
WantedBy=sockets.target

套接字将按照此单元中定义的顺序传递,因此端口 80 将是文件描述符 3,端口 443 将是文件描述符 4。如果您之前启用或启动了该服务,您现在应该停止它,并启用 nginx.socket。当您的系统启动时,nginx 将不会运行,而是在您在浏览器中访问网站时启动。有了这个,您可以进一步加强服务;例如,在许多情况下,您现在可以在服务文件中设置 PrivateNetwork=True,阻止 nginx 访问外部网络,因为 systemd 创建的套接字足以通过网络提供网站服务。请注意,这将在 nginx 服务的日志中打印警告:2020/08/29 19:33:20 [notice] 254#254: using inherited sockets from "3:4;"

PID 文件

nginx 编译为默认使用 /run/nginx.piduser 无法写入该文件。我们可以创建一个 user 可以写入的目录,并将 PID 文件放在那里。例如,可以使用 RuntimeDirectorysystemd.exec(5))来完成此操作。

编辑 nginx.service 以配置 PID 文件

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
RuntimeDirectory=nginx
PIDFile=/run/nginx/nginx.pid
ExecStart=
ExecStart=/usr/bin/nginx -g 'pid /run/nginx/nginx.pid; error_log stderr;' 
ExecReload=
ExecReload=/usr/bin/nginx -s reload -g 'pid /run/nginx/nginx.pid; error_log stderr;'

/var/lib/nginx

nginx 默认编译为将临时文件存储在 /var/lib/nginx 中。

提示: 运行 $ nginx -V 查看所有编译时选项

你可以通过例如使用 StateDirectory (systemd.exec(5)) 来赋予user 对此目录的写入权限

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
StateDirectory=nginx

/var/log/nginx

nginx 默认编译为将访问日志存储在 /var/log/nginx 中。

你可以通过例如使用 LogsDirectory (systemd.exec(5)) 来赋予user 对此目录的写入权限

/etc/systemd/system/nginx.service.d/user.conf
[Service]
...
LogsDirectory=nginx

使用 systemd 运行用户服务

如果你想运行一个完全由非特权用户控制和配置的服务器实例,请考虑使用 nginx-user-serviceAUR

systemd 的替代脚本

在纯 systemd 上,你可以获得 chroot + systemd 的优势。[4] 基于设置 user group 和 pid 以及

/etc/nginx/nginx.conf
user http;
pid /run/nginx.pid;

文件的绝对路径是 /srv/http/etc/nginx/nginx.conf

/etc/systemd/system/nginx.service
[Unit]
Description=nginx (Chroot)
After=network.target

[Service]
Type=forking
PIDFile=/srv/http/run/nginx.pid
RootDirectory=/srv/http
ExecStartPre=/usr/bin/nginx -t -c /etc/nginx/nginx.conf
ExecStart=/usr/bin/nginx -c /etc/nginx/nginx.conf
ExecReload=/usr/bin/nginx -c /etc/nginx/nginx.conf -s reload
ExecStop=/usr/bin/nginx -c /etc/nginx/nginx.conf -s stop

[Install]
WantedBy=multi-user.target

没有必要设置默认位置,nginx 默认加载 -c /etc/nginx/nginx.conf,但这是一个好主意。

或者,你可以ExecStart 作为 chroot 运行,并将参数 RootDirectoryStartOnly 设置为 yes (参见 systemd.service(5)) 或者在挂载点之前作为有效路径或 systemd 路径启动它(参见 systemd.path(5)) 是可用的。

/etc/systemd/system/nginx.path
[Unit]
Description=nginx (Chroot) path
[Path]
PathExists=/srv/http/site/Public_html
[Install]
WantedBy=default.target

启用 创建的 nginx.path 并在 /etc/systemd/system/nginx.service 中将 WantedBy=default.target 更改为 WantedBy=nginx.path

单元文件中的 PIDFile 允许 systemd 监控进程(需要绝对路径)。如果不需要,你可以更改为默认的 oneshot 类型,并从单元文件中删除引用。

Nginx 美化器

nginxbeautifierAUR 是一个用于美化和格式化 nginx 配置文件的命令行工具。

更好的标头管理

Nginx 有一个相当违反直觉的标头管理系统,其中标头只能在一个上下文中定义,任何其他标头都会被忽略。为了解决这个问题,我们可以安装 headers-more-nginx 模块。

安装 软件包 nginx-mod-headers-more 软件包。这会将模块安装到 /usr/lib/nginx/modules 目录。

要加载模块,请将以下内容添加到主 nginx 配置文件的顶部。

/etc/nginx/nginx.conf
load_module "/usr/lib/nginx/modules/ngx_http_headers_more_filter_module.so";
...

基本身份验证

基本身份验证需要创建密码文件。密码文件可以使用 apache 软件包提供的 htpasswd 程序或使用 nginx_passwdAUR 进行管理,后者提供了 nginx-passwd - 详细信息请参见 GitHub 源代码

使用 php-legacy

安装 php-legacy-fpm 而不是 php-fpm,并确保 PHP 已正确安装和配置。

PHP-LEGACY-FPM 的主配置文件是 /etc/php-legacy/php-fpm.conf。对于基本用法,默认配置应该足够了。

fastcgi_pass 参数的 Unix 套接字也需要调整,通常是

fastcgi_pass unix:/run/php-fpm-legacy/php-fpm.sock;

然后 启动/启用 php-legacy-fpm.service

故障排除

配置验证

# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

错误:您正在查找的页面暂时不可用。请稍后重试。(502 Bad Gateway)

这是因为 FastCGI 服务器尚未启动,或者使用的套接字权限错误。

尝试 此答案 以修复 502 错误。

在 Arch Linux 中,上述链接中提到的配置文件是 /etc/php/php-fpm.conf

错误:未指定输入文件

1. 验证 /etc/php/php.ini 中的变量 open_basedir 是否包含在 nginx.conf 中指定为 root 参数的正确路径(通常是 /usr/share/nginx/)。当使用 PHP-FPM 作为 PHP 的 FastCGI 服务器时,你可以在 nginx.conf 中旨在处理 PHP 文件的 location 代码块中添加 fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/";

2. 另一种情况是,nginx.conflocation ~ \.php$ 部分中的 root 参数错误。确保 root 指向与同一服务器中 location / 中相同的目录。或者你可以将 root 设置为全局的,不要在任何 location 部分中定义它。

3. 检查权限:例如,用户/组为 http,目录为 755,文件为 644。记住到 html 目录的整个路径都应具有正确的权限。参见 文件权限和属性#批量 chmod 以批量修改目录树。

4. 你没有包含脚本完整路径的 SCRIPT_FILENAME。如果 nginx 的配置 (fastcgi_param SCRIPT_FILENAME) 正确,则此类错误意味着 PHP 未能加载请求的脚本。通常这只是一个权限问题,你可以以 root 身份运行 php-cgi

# spawn-fcgi -a 127.0.0.1 -p 9000 -f /usr/bin/php-cgi

或者你应该创建一个组和用户来启动 php-cgi

# groupadd www
# useradd -g www www
# chmod +w /srv/www/nginx/html
# chown -R www:www /srv/www/nginx/html
# spawn-fcgi -a 127.0.0.1 -p 9000 -u www -g www -f /usr/bin/php-cgi

5. 如果你正在使用 chrooted nginx 运行 php-fpm,请确保在 /etc/php-fpm/php-fpm.d/www.conf(或在旧版本上为 /etc/php-fpm/php-fpm.conf)中正确设置了 chroot

警告:无法构建最佳 types_hash

当启动 nginx.service 时,进程可能会记录消息

[warn] 18872#18872: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size

要修复此警告,请增加 http 代码块内这些键的值 [5] [6]

/etc/nginx/nginx.conf
http {
    types_hash_max_size 4096;
    server_names_hash_bucket_size 128;
    ...
}

无法分配请求的地址

来自 nginx.service 单元状态 的完整错误是

[emerg] 460#460: bind() to A.B.C.D:443 failed (99: Cannot assign requested address)

即使你的 nginx 单元文件配置为在 systemd 的 network.target 之后运行,nginx 也可能尝试监听已配置但尚未添加到任何接口的地址。通过手动为 nginx 启动 来验证是否是这种情况(从而显示 IP 地址已正确配置)。配置 nginx 监听任何地址将解决此问题。现在,如果你的用例需要监听特定地址,一种可能的解决方案是重新配置 systemd。

要在所有配置的网络设备启动并分配 IP 地址后启动 nginx,请将 network-online.target 附加到 nginx.service 中的 After=,并启动/启用 systemd-networkd-wait-online.service

参见