MENU

安全又快速的 Nginx 网站配置

2019 年 06 月 10 日 • 技术流

这篇文章是我的 Linux 操作系统课大作业(PPT 材料)。发在这里是供自己和大家参考,如有错误或不当敬请指正。

从官方源安装 Nginx

安装 Nginx 其实很简单。

大多数的 Linux 发行版自带的源已经包含 Nginx。因此只需要一行命令:

apt install nginx

(或 yum install nginx 等,取决于你的发行版,这里以 Debian 系列为例)

但是,如果发行版比较老,自带的 Nginx 也是旧版本的,会缺乏新特性支持。为了配置安全又快速的网站,我们需要用到一些现代特性,因而需要自己编译。

三个现代特性

我们主要会用到这些现代特性:

HTTP/2

目前 HTTP 协议最新一代。正式版发布于 2015 年 5 月。前身是 SPDY 协议,包含多路复用等提升网页加载速度的技术。Nginx 从 1.9.5 开始官方支持(http_v2_module 取代 ngx_http_spdy_module)。建议使用更新版本避免早期实现的小 BUG。

Brotli

Google 推出的无损压缩算法,对常见静态资源有更高的压缩率。“当 Brotli 压缩级别为 1 时,压缩率比 Gzip 压缩等级为 9(最高)时还要高”。Nginx 官方未提供内置模块,需要自行编译置入

参考资料:《启用 Brotli 压缩算法,对比 Gzip 压缩 CDN 流量再减少 20%》

注:由于兼容性及其他原因,HTTP/2 与 Brotli 事实上只工作于 HTTPS 环境下。

TLS 1.3

目前 TLS 最新的标准。经历了几次草案,正式版发布于 2018 年 8 月。相较于旧版本大幅改进了性能和安全性。Nginx 从 1.13.0 开始提供支持。建议使用更新版本避免早期实现的 BUG,并且增加对 Early Data 的支持(Nginx 1.15.4 起)。需要配合 OpenSSL 1.1.1 版本才能开启 TLS 1.3(由于该 OpenSSL 过新,通常意味着需要手动编译)。

参考资料:《SSL/TLS 历史与前沿导览》

结论

综上,我们的最佳选择其实是使用最新版本的 Nginx。

截至本人撰写该材料,Nginx 的最新主线版本是 1.17.0,以下均以此版本展开。

编译安装 Nginx

为了避免手动新建用户,撰写 Service 文件等的麻烦,我们先从官方源安装 Nginx,再手动编译 Nginx。但在编译 Nginx 本体之前,我们先安装相关开发工具的依赖。以下操作均以 root 身份进行。

apt install autoconf libtool automake perl make gcc g++ patch git zlib1g-dev libxslt-dev libgd-dev geoip-database libgeoip-dev

下载最新版本的 OpenSSL 以提供 TLS 支持。暂时不需要编译,稍后会与 Nginx 一同编译。

mkdir /usr/local/openssl
cd /usr/local/src
wget https://www.openssl.org/source/openssl-1.1.1b.tar.gz
tar xzf openssl-1.1.1b.tar.gz
cd openssl-1.1.1b
./config

下载 pcre 源码。这个库提供正则表达式的支持。

mkdir -p /root/nginx-source/pcre
cd /root/nginx-source/pcre
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.42.tar.gz
tar -zxvf pcre-8.42.tar.gz

接着下载 ngx_brotli 源码。

cd ~
git clone https://github.com/eustas/ngx_brotli.git # 这并非 Google 的官方仓库,但相比较版本更新
cd ngx_brotli
git submodule update --init

同很多其他程序一样,Nginx 也采用 Makefile 文件方便用户编译,但我们应该手动配置一些参数再开始编译。

cd /usr/local/src
wget http://nginx.org/download/nginx-1.17.0.tar.gz
tar xzf nginx-1.17.0.tar.gz
cd /usr/local/src/nginx-1.17.0
./configure  --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-1.17.0=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module --with-openssl=/usr/local/src/openssl-1.1.1b --with-pcre=/root/nginx-source/pcre/pcre-8.42 --add-module=/root/ngx_brotli 
make

编译后安装只需一句:

make install

因为我们是要替代原有 Nginx 的,所以再进行一些工作:

rm -rf /usr/sbin/nginx # 删除原有 Nginx 程序
ln -s /usr/share/nginx/sbin/nginx /usr/sbin/nginx # 建立软链接
nginx -t # 测试 Nginx 配置文件正确性
nginx -V
service nginx restart
apt-mark hold nginx # 禁止系统自带 Nginx 包更新

可见,这样一来,安装 Nginx 的过程变得麻烦耗时。好在我们其实可以选择用第三方的 Nginx 包

从第三方源安装 Nginx

SB 仓库(烧饼博客提供)就是一个不错的选择,其网址是:

https://mirror.xtom.com/sb/nginx/ (主站)
https://mirror.xtom.com.hk/sb/nginx/ (香港镜像)

该源使用 OpenSSL 1.1.1 编译,支持 TLS 1.3、GeoIP2、Brotli 压缩等,会保持跟进最新 Nginx 版本,符合我们需求。

于是 Nginx 的安装再次变得简单:

curl https://mirror.xtom.com/sb/nginx/public.key | apt-key add - # 添加源公钥
echo "deb https://mirror.xtom.com/sb/nginx/ stretch main" > /etc/apt/sources.list.d/sb-nginx.list # 添加源
apt update && apt install nginx # 从源安装

Nginx Conf 内容

全局配置

SSL 方面遵循《SSL/TLS 部署最佳实践》(SSL and TLS Deployment Best Practices):

ssl_protocols           TLSv1.2 TLSv1.3;
ssl_session_cache       shared:SSL:10m;
ssl_session_timeout     1h;
ssl_stapling            on;
ssl_stapling_verify     on;
ssl_prefer_server_ciphers on; 
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256';
ssl_certificate /yourcert;
ssl_certificate_key /yourkey;
ssl_dhparam /dhparams.pem;
ssl_early_data off;

对于压缩,优先使用 Brotli 压缩,兼容 Gzip:

brotli on;
brotli_comp_level 5;
brotli_min_length 256;
brotli_types text/plain text/javascript text/css text/xml text/x-component application/javascript application/x-javascript application/xml application/json application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype;

gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/javascript text/css text/xml text/x-component application/javascript application/x-javascript application/xml application/json application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;

安全协议头(Headers),根据实际情况配置:https://securityheaders.com/

add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # 强制 HTTPS。请务必理解后再添加该头,不容易反悔。
add_header Content-Security-Policy "upgrade-insecure-requests" always; # 内容安全策略,请自行查阅相关资料。

配置默认的 HTTP 和 HTTPS 服务器,以防全网嗅探:

server {
    listen 0.0.0.0:80 default_server;
    listen [::]:80 default_server;
    server_name _;

    add_header X-Frame-Options "DENY" always; # 这里又有 add_header,Nginx 会以该区块为准,忽略全局 add_header
    
    return 403;
}
server {
    listen 0.0.0.0:443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name _;

    ssl_stapling            off;
    ssl_certificate /your_invalid_crt;
    ssl_certificate_key /your_invalid_crt_key;

    add_header X-Frame-Options "DENY" always;

    return 403;
}

虚拟主机

最后就是各个网站的配置了。例如:

server {
    listen 0.0.0.0:80;
    listen [::]:80;
    server_name www.xuab.net;
    
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / {
        return 301 https://www.xuab.net$request_uri;
    }
}
server {
    listen 0.0.0.0:443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.xuab.net;

    location / {
        root    /var/www/www.xuab.net;
        location ~ ^/.+\.php {
            fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
            fastcgi_index  index.php;
            fastcgi_split_path_info ^(.+\.php)(/?.+)$;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
            include        fastcgi_params;
            fastcgi_pass unix:/var/run/php/php7.3-fpm.sock; # 这是 Unix Socket,你也可以按实际情况换成普通的 Socket
        }
        if (!-e $request_filename)
        {
            rewrite ^(.+)$ /index.php?q=$1 last; # 地址重写,或称“伪静态”
        }
    }
}

这是以典型 PHP 博客(WordPress)为例。其他场景可能会想要配置反向代理。

效果

可以使用浏览器的开发者工具观察到上述三个现代特性:

HTTP/2

brotli

TLS 1.3

添加新评论

已有 2 条评论
  1. Hugefiver Hugefiver

    为什么不试试神奇的boringSSL呢(

    1. @Hugefiver因为会很 boring 啊(