Nginx 反向代理 + Let's Encrypt SSL:从零搭建安全 Web 服务完整指南

说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。

任何 Web 应用上线前都绕不开两件事:反向代理和 HTTPS。本文用一台全新的 Ubuntu VPS,手把手带你完成 Nginx 反向代理配置、Let's Encrypt 免费 SSL 证书申请、自动续期,以及常见的坑和排查方法。

为什么需要反向代理?

直接把 Node.js / Python / Go 应用暴露在 80/443 端口上有很多问题:

  • 权限问题:非 root 用户无法绑定 1024 以下端口
  • 无法同时服务多个站点:一个端口只能绑一个应用
  • 没有 HTTPS:现代浏览器对 HTTP 网站会标记"不安全"
  • 缺少缓存和压缩:每次请求都打到后端,性能差

Nginx 作为反向代理,站在客户端和后端应用之间,解决以上所有问题。

环境准备

本文基于以下环境(其他 Linux 发行版步骤类似):

BASH
# 操作系统
Ubuntu 22.04 LTS

# 需要准备
- 一个已解析到服务器 IP 的域名(本文用 example.com)
- 服务器已开放 80、443 端口
- 一个运行在 localhost:3000 的 Web 应用

第一步:安装 Nginx

BASH
# 更新包索引
sudo apt update

# 安装 Nginx
sudo apt install -y nginx

# 启动并设置开机自启
sudo systemctl start nginx
sudo systemctl enable nginx

# 验证安装
curl -I http://localhost

如果看到 200 OK,说明 Nginx 已经正常运行。

第二步:配置反向代理

创建一个 Nginx 配置文件。建议为每个站点单独一个文件,方便管理:

BASH
sudo nano /etc/nginx/sites-available/example.com

写入以下内容:

NGINX
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # 日志路径
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

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

        # 透传客户端真实 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;

        # WebSocket 支持(如果需要)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # 静态资源缓存(如果有)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
        proxy_pass http://127.0.0.1:3000;
        expires 7d;
        add_header Cache-Control "public, immutable";
    }
}

启用这个配置并重载 Nginx:

BASH
# 创建软链接启用站点
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

# 删除默认配置(可选)
sudo rm /etc/nginx/sites-enabled/default

# 测试配置语法
sudo nginx -t

# 重载 Nginx
sudo systemctl reload nginx

常见错误:如果 nginx -t 报错 could not openalready exists,检查 /etc/nginx/sites-enabled/ 下是否有重复的软链接。

第三步:申请 Let's Encrypt SSL 证书

Let's Encrypt 提供免费的 SSL 证书,通过 Certbot 工具自动完成申请和配置。

BASH
# 安装 Certbot 和 Nginx 插件
sudo apt install -y certbot python3-certbot-nginx

# 自动申请证书并配置 Nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot 会自动:

  1. 验证你对域名的控制权(通过 HTTP-01 挑战)
  2. 下载 SSL 证书
  3. 修改你的 Nginx 配置,添加 SSL 相关指令
  4. 设置 HTTP → HTTPS 自动跳转

完成后你的 Nginx 配置会变成类似这样:

NGINX
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    # Certbot 自动添加的跳转
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    # Certbot 自动添加的 SSL 配置
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # 你原来的反向代理配置...
    location / {
        proxy_pass http://127.0.0.1:3000;
        # ...
    }
}

访问 https://example.com,浏览器应显示连接安全/HTTPS 正常。

第四步:配置证书自动续期

Let's Encrypt 的证书有效期是 90 天,过期后浏览器会报警告。Certbot 安装时会自动创建 systemd timer,但我们需要确认它正常工作:

BASH
# 查看自动续期定时器状态
sudo systemctl status certbot.timer

# 手动测试续期是否正常(不会真正续期,只是模拟)
sudo certbot renew --dry-run

如果 dry-run 没有报错,说明自动续期已经配置好了。

如果你想更主动地控制,可以加一个 cron 任务作为双保险:

BASH
# 编辑 root 用户的 crontab
sudo crontab -e

# 添加以下行(每天凌晨 3 点检查续期)
0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

--deploy-hook 参数会在证书成功续期后自动重载 Nginx,确保新证书立即生效。

第五步:安全加固(可选但推荐)

基本的反向代理和 SSL 就够用了,但如果你想更安全,可以加上这些配置:

NGINX
server {
    # ... 之前的配置 ...

    # 安全响应头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # 隐藏 Nginx 版本号
    server_tokens off;

    # 限制请求体大小(防止大文件上传攻击)
    client_max_body_size 10m;

    # 禁止访问隐藏文件(如 .git、.env)
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

常用安全头说明

Header 作用
Strict-Transport-Security 强制浏览器使用 HTTPS(HSTS)
X-Content-Type-Options 防止浏览器 MIME 类型嗅探
X-Frame-Options 防止页面被嵌入 iframe(防点击劫持)
X-XSS-Protection 启用浏览器内置 XSS 过滤

常见问题排查

问题 1:502 Bad Gateway

原因:Nginx 无法连接到后端服务。

BASH
# 检查后端是否在运行
curl http://127.0.0.1:3000

# 检查 Nginx 错误日志
sudo tail -f /var/log/nginx/example.com.error.log

# 检查端口是否被占用
sudo ss -tlnp | grep 3000

常见原因

  • 后端应用没有启动
  • 后端监听的端口与 Nginx 配置不一致
  • 防火墙阻止了本地连接

问题 2:SSL 证书申请失败

BASH
# Certbot 报错 "Challenge failed"
# 最常见原因是 80 端口不通

# 检查防火墙
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# 检查是否有其他服务占用 80 端口
sudo ss -tlnp | grep :80

问题 3:WebSocket 连接断开

如果用了 WebSocket 但连接频繁断开,检查这些配置:

NGINX
location /ws {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    # WebSocket 需要更长的超时时间
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

问题 4:混合内容警告(Mixed Content)

页面加载了 HTTP 资源但页面本身是 HTTPS。检查你的应用配置,确保:

BASH
# 在应用代码中确保生成的 URL 使用 HTTPS
# 例如 Node.js + Express
app.set('trust proxy', 1);  # 信任第一层代理
app.use((req, res, next) => {
    if (req.headers['x-forwarded-proto'] === 'https') {
        res.locals.baseUrl = 'https://example.com';
    }
    next();
});

完整的较完整的配置模板

把上面的内容综合起来,一个完整的较完整的 Nginx 配置长这样:

NGINX
# /etc/nginx/sites-available/example.com

# HTTP → HTTPS 跳转
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 主配置
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL 证书(Certbot 自动管理)
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    server_tokens off;

    # 日志
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    # 反向代理
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
    }
}

总结

搭建一个安全的 Web 服务并不复杂,核心就三步:

  1. Nginx 反向代理:让 Nginx 处理客户端请求,转发到后端应用
  2. Let's Encrypt SSL:一行命令申请免费证书
  3. 自动续期:Certbot timer 保证证书不过期

Let's Encrypt 的 DV 证书在传输加密强度上可以满足大多数网站 HTTPS 需求,但它只验证域名控制权,不验证企业主体身份;如果业务需要组织身份背书或合规审计,应评估 OV/EV 或商业 CA。如果遇到问题,先看 Nginx 错误日志,大部分问题都能在那里找到答案。


本文所有配置示例均可直接复制使用,只需将 example.com 替换成你自己的域名。

评论