[小白参阅系列] 第一篇 手搓 Nginx 之配置文件与 Non-root 启动

本教程初版于 https://www.nodeseek.comhttp://127.0.0.1:5001/post-37224-1, 由于坛内限制编辑故开新帖, 着重更新了配置文件部分, 同时引入 Non root 启动 Nginx 进一步确保安全.

参考配置文件 /etc/nginx/nginx.conf

# 是否以守护进程方式运行 Nginx
# 试图直接以 Nginx 用户启动(Type=exec)而不是 root 用户以 forking 模式启动时, 配置为 off
daemon on;

# 配置 PID 文件位置
# 默认保存于 /var/run/nginx/nginx.pid
# pid /run/nginx/nginx.pid;

# 配置 worker 进程启动用户组及用户
# 仅以 Root 用户启动 master 进程时生效
user nginx nginx;

# Worker 线程数, 自动即可
worker_processes auto;

# CPU 亲和性配置, 自动即可
worker_cpu_affinity auto;

# worker 进程优先级
worker_priority -20;

# ulimit -n, 尽量提前解除 ulimit 限制(虽然大部分情况下不是高并发)
worker_rlimit_nofile 51200;

events
{
    use epoll;
    worker_connections 10240;
    multi_accept on;
}

http
{
    include mime.types;
    # set_real_ip_from 0.0.0.0/0;
    # real_ip_header CF-Connecting-IP;

    default_type  application/octet-stream;
    charset utf-8;

    http2 on;

    log_format details '[$time_local][$status]|[Client] "$remote_addr" |[Host] "$host" |[Refer] "$http_referer" |[UA] "$http_user_agent" |[REQ] "$request" |[CONNECT] "$connection_requests" |[TIME] "$request_time" |[LENGTH] "$bytes_sent" |[UPSTREAM] "$upstream_addr" |[U_HEAD_TIME] "$upstream_header_time" |[U_CON_TIME] "$upstream_connect_time" |[U_RSP_TIME] "$upstream_response_time" |[U_STATUS] "$upstream_status" |[U_LENGTH] "$upstream_response_length"';

    server_names_hash_bucket_size 512;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 50m;

    # Perf
    access_log off;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    reset_timedout_connection on;
    client_body_timeout 10;
    send_timeout 2;
    keepalive_timeout 60;

    # SSL
    # Check https://sysin.org/blog/tlsv1-3-support/ for more details
    # 确信你的设备完全支持 TLSv1.3 可舍弃 TLSv1.2 支持, 进一步提高 HTTPS 性能
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    # SSL 协议, 舍弃对极老旧设备/应用等的兼容性, 进一步提高 HTTPS 性能
    # Ref: https://developers.cloudflare.com/ssl/reference/cipher-suites/recommendations/
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_ecdh_curve X25519:P-256:P-384:P-224:P-521;
    ssl_session_cache shared:SSL:30m;
    ssl_session_timeout 1d;
    # 此处原教程有误, 复用 session 应当设置此项为 off, 特此纠正
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    # DNS, 根据机器实际情况设置
    resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=60s;
    resolver_timeout 2s;
    ssl_early_data on;
    ssl_buffer_size 8k;

    ##
    # Connection header for WebSocket reverse proxy
    ##
    map $http_upgrade $connection_upgrade {
      default upgrade;
      '' close;
    }

    # fastcgi
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_intercept_errors on;

    # compress
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 6;
    gzip_types
        # text/html
        text/css
        text/javascript
        text/xml
        text/plain
        text/x-component
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/rss xml
        application/atom xml
        font/truetype
        font/opentype
        application/vnd.ms-fontobject
        image/svg xml;
    gzip_vary on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable "MSIE [1-6]\.";
    brotli on;
    brotli_comp_level 6;
    brotli_types
        # text/html
        text/css
        text/javascript
        text/xml
        text/plain
        text/x-component
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/rss xml
        application/atom xml
        font/truetype
        font/opentype
        application/vnd.ms-fontobject
        image/svg xml;

    # Others
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver:10m;
    server_tokens off;
    ## QUIC
    http3 on;
    http3_hq on;
    quic_retry on;
    add_header Alt-Svc 'h3=":443"; ma=86400';

    # Default server
    server
    {
        listen 80 default_server;
        listen 443 ssl default_server;
        listen 443 quic reuseport;
        server_name _;
        ssl_reject_handshake on;
        location /connection-test {
            default_type  application/json;
            return 200 '{"code":0, "message":""}';
        }
        location / {
            return 444;
        }
        access_log  /www/logs/nxdomain.com.log details;
    }

    # Include other conf
    include /etc/nginx/conf.d/*.conf;
}

Non-root NGINX

https://www.nodeseek.comhttp://127.0.0.1:5001/post-59478-3 提到的 Non-root 启动 Nginx, 以下为安装流程和配置. 未说明详情则参考原教程.

需要指出: 以非 root 用户运行 Nginx 会有很多莫名其妙的权限问题, 实在不建议新手! 此处仅供参考, 算是为后来者排坑了.

  • 编译安装 Nginx

    • 更新系统
    • 安装编译依赖
    • 前置准备
    • 下载源代码
    • configure
    • make
    • make install
  • 配置 systemd 持久化

    此步内容和原教程不一致, 应当注意

    Ref: https://www.sherbers.de/running-nginx-without-root/

    [Unit]
    Description=The NGINX HTTP and reverse proxy server
    After=syslog.target network-online.tarnaget remote-fs.target nss-lookup.target
    Wants=network-online.target
    
    [Service]
    # 此处不再以 forking 方式启动 Nginx
    Type=exec
    # 指定使用用户组 nginx 内的用户 nginx 启动 master 进程,
    # 启动 worker 进程亦然
    User=nginx
    Group=nginx
    
    # ===== 限制运行各类文件的目录 ======
    
    # 在 /run 中创建一个名为 nginx 的目录, 并根据 User= 选项调整权限,
    # 存放 PID 文件. 虽然以 exec 方式启动不需要 PID 文件但是没有选项可以关闭
    RuntimeDirectory=nginx
    # 由于 NGINX 不再以 root 身份启动, 无法创建自己的日志和缓存目录,
    # 需要指定以下两项, 以确保目录 /var/log/nginx 和 /var/cache/nginx
    # 存在且当前用户可读写
    LogsDirectory=nginx
    CacheDirectory=nginx
    ConfigurationDirectory=nginx
    
    # ===== 启动相关 ======
    
    ExecStartPre=/bin/rm -rf /dev/shm/nginx
    ExecStartPre=/bin/mkdir /dev/shm/nginx
    ExecStartPre=/bin/chmod 711 /dev/shm/nginx
    ExecStartPre=/bin/mkdir /dev/shm/nginx/tcmalloc
    ExecStartPre=/bin/chmod 0777 /dev/shm/nginx/tcmalloc
    
    ExecStart=/usr/sbin/nginx
    
    ExecStop=/usr/sbin/nginx -s stop
    ExecStopPost=/bin/rm -rf /dev/shm/nginx
    
    ExecReload=/bin/kill -HUP $MAINPID
    
    Restart=on-failure
    RestartSec=10s
    
    # ==== Filesystem access ====
    # 文件系统权限
    
    # 禁止对操作系统/配置文件/本地挂载点进行任何修改
    # 开启此选项之后, 可使用 ReadWritePaths= 来将某些
    # 特定的目录改为读写模式
    ProtectSystem=strict
    # 对该单元内的进程屏蔽 /home, /root, /run/user 目录(内容为空且不可访问)
    ProtectHome=true
    # 在进程的文件系统名字空间中挂载私有的 /tmp 与 /var/tmp 目录
    # 注意: 进程终止后会立即清除
    PrivateTmp=true
    # 该单元内的进程设置一个全新的 /dev 挂载点, 阻止访问实际的设备
    # Nginx 一般无需访问物理设备
    PrivateDevices=true
    # 仅允许以只读模式访问 /sys/fs/cgroup
    # 除了容器管理程序, 其他服务不应该对 cgroups 拥有控制权
    ProtectControlGroups=true
    # 禁止显式加载/卸载内核模块
    # Nginx 正常情况下完全无操作内核模块的需求
    ProtectKernelModules=true
    # 保护内核变量
    # Nginx 正常情况下完全无操作内核变量的需求
    ProtectKernelTunables=true
    # 需要注意, 由于后面我们将访问日志保存到 /www/logs 下
    # 需要指定 /www, 至少 /www/logs 可读写, 否则默认只读
    ReadWritePaths=/www
    
    # ==== network ====
    # 非 root 用户需要指定允许监听 Unix Socket, IPv4, IPv6
    RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
    
    # ==== misc ====
    # 安全杂项, 可以但是不建议忽略
    
    SystemCallArchitectures=native
    # 确保服务进程及其所有子进程永远无法通过execve()获得任何新权限
    NoNewPrivileges=true
    # 阻止程序启用实时调度(可用于长时间独占 CPU 时间)造成 DoS 攻击
    RestrictRealtime=true
    # 禁止尝试创建同时可写和可执行的内存映射
    MemoryDenyWriteExecute=true
    # 阻止访问内核日志
    ProtectKernelLogs=true
    LockPersonality=true
    # 防止读取/更改当前主机名或域名
    ProtectHostname=true
    RemoveIPC=true
    # 阻止 set-user-ID(SUI) 或 set-group-ID (SGID),
    # 以阻止当前程序提升权限并允许获取其他用户身份
    RestrictSUIDSGID=true
    # 阻止写入硬件时钟或系统时钟
    ProtectClock=true
    
    # ==== capabilities ====
    # 允许非 root 时访问低位(<1024)端口
    AmbientCapabilities=CAP_NET_BIND_SERVICE
    
    [Install]
    WantedBy=multi-user.target
    
  • 配置文件调优

    在前面提到的配置文件的基础上, 需要进行如下修改

    • daemon 配置为 off(前面配置了 exec 方式使用 nginx:nginx 用户组:用户启动 Nginx)
    • 去除 pid 一行的注释, 将 PID 文件保存于 /run/nginx 下
    • 注释掉 user nginx nginx; 一行(虽然不注释也没事, 启动的时候 warn 一下 the "user" directive makes sense only if the master process runs with super-user privileges, ignored 而已
    • 注释掉 worker_priority -20; 一行, nginx 没有权限调整进程优先级
  • 扫尾

    需要尤其指出权限问题:

    • 自定义的 nginx 需要读取的目录/文件, 如 /www, /etc/nginx/certs 等, 必须是 700 或更高权限, 原因不清楚, 需要看源码(想不清楚为什么要执行的权限). 证书目录建议 700.
    # 无需我们自己创建缓存目录
    # mkdir /var/cache/nginx
    # 创建 Nginx 配置文件目录. 你的网站的配置文件就得存这里
    mkdir /etc/nginx/conf.d
    # 创建证书目录, 记得把证书存这里别乱存
    mkdir /etc/nginx/certs
    # 网站文件目录
    mkdir /www
    # 网站日志目录
    mkdir /www/logs
    # 新建 nginx 用户(用户组同名), 赋予最低权限
    useradd -M -s /sbin/nologin nginx
    # 修改网页目录权限
    chown -R nginx:nginx /www
    chmod -R 700 /www
    # 修改证书目录权限
    # 如果采用 acme.sh 获取并安装证书到 /etc/nginx/certs,
    # 需要尤其注意权限问题.
    chown -R nginx:nginx /etc/nginx/certs
    chmod -R 700 /etc/nginx/certs
    
  • 运行!!!

2024/02/07, Rev2.1, Copyright ©2024 Hantong Chen

点赞
  1. 叶公说道:

    帮顶

  2. patience说道:

    绑定

发表回复

电子邮件地址不会被公开。必填项已用 * 标注

×
订阅图标按钮