Nginx 反向代理 + 免费 HTTPS 证书,一次配好不返工

Nginx 的配置文件看着长,其实常用的就那么几块。这里给一份我一直在用的模板,以及每次配完必做的检查项。

一、安装与目录约定

bash
sudo apt install -y nginx
sudo systemctl enable --now nginx
nginx -v
  • /etc/nginx/sites-available/ 放配置文件;
  • /etc/nginx/sites-enabled/ 放软链接,只有链进来的才会被加载;
  • /var/log/nginx/ 日志,access.logerror.log 分开;
  • /etc/nginx/nginx.conf 是主配置,里面 include 了上面两个目录。
bash
sudo ln -s /etc/nginx/sites-available/baize.conf /etc/nginx/sites-enabled/
sudo nginx -t              # 每次改完必跑,语法错了它会告诉你行号
sudo systemctl reload nginx  # 平滑重载,不断连接

二、托管静态站点

nginx
server {
    listen 80;
    listen [::]:80;
    server_name bzii.cn www.bzii.cn;

    root /var/www/baize;
    index index.html;

    # SPA 路由:找不到文件就回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源长缓存(前提是文件名带 hash,否则别这么干)
    location ~* .(css|js|png|jpg|jpeg|gif|svg|webp|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1024;
}

三、反向代理本机服务

比如有个 Node 服务跑在 127.0.0.1:3000

nginx
server {
    listen 80;
    server_name api.bzii.cn;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # 这几条是 WebSocket 与真实 IP 的关键
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_connect_timeout 10s;
        proxy_read_timeout 60s;
    }

    client_max_body_size 20m;   # 上传大小,默认只有 1m,很容易踩
}

127.0.0.1 而不是 localhost:后者可能解析到 IPv6 的 ::1,如果你的服务只监听 IPv4,就会 502。

四、签发 Let's Encrypt 证书

bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d www.bzii.cn -d www.bzii.cn

certbot 会自动改配置、加 443 端口和跳转。写入后的 server 块大概长这样:

nginx
server {
    listen 443 ssl http2;
    server_name bzii.cn www.bzii.cn;

    ssl_certificate     /etc/letsencrypt/live/www.bzii.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.bzii.cn/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    root /var/www/baize;
    index index.html;
    location / { try_files $uri $uri/ /index.html; }
}

# 80 端口全部跳转 HTTPS
server {
    listen 80;
    server_name bzii.cn www.bzii.cn;
    return 301 https://$host$request_uri;
}

自动续期:certbot 装完自带 systemd timer,检查一下就行。

bash
systemctl status certbot.timer
sudo certbot renew --dry-run    # 演习一次,确认续期链路通畅

证书有效期 90 天,certbot 会在剩余 30 天时自动续。如果 80 端口被防火墙挡了,HTTP-01 校验会失败,这时改用 DNS-01(Cloudflare:certbot-dns-cloudflare)。

五、加几行安全头

nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

HSTS 加上之后浏览器会强制走 HTTPS,回滚 HTTP 会很麻烦,确认站点稳定了再加。

六、踩坑排查顺序

现象排查顺序
502 Bad Gateway后端没起来 → curl 127.0.0.1:3000proxy_pass 端口对不对
403 Forbidden目录权限 → www-data 有没有读权限 → index 文件存不存在
404root 路径写错,或 try_files 回退顺序不对
改了没生效nginx -t、没 reload,或改错了 sites-available 但没软链接
证书签发失败域名没解析到本机 IP / 80 端口不通 / CDN 挡住了校验
bash
sudo tail -f /var/log/nginx/error.log   # 实时看错误日志
sudo nginx -T | grep -n "server_name"   # 确认到底加载了哪些配置

配完用 SSL Labs 跑一次评级,A+ 并不难拿。