运维实战

一台服务器怎么跑多个网站?Nginx 反向代理与 HTTPS 配置实战

Luffy 10 阅读 0 评论

一台服务器怎么跑多个网站?答案是:一个 Nginx 反向代理,加上一份规范的 HTTPS 配置。这篇文章把"单 IP 多站点"的完整部署过程拆开讲——从"一个公网 IP 怎么装多个站"到"证书怎么签才不踩老设备兼容坑",全程可复现,示例域名统一用 example.com 系列。

单服务器多站点架构

问题:只有一个公网 IP,却要跑多个站

服务器只有 80 / 443 两个"对外入口",但背后可能要跑:企业官网、商城、博客、API 服务、内部工具…… 总不能一个站开一个公网端口,那既不安全也不优雅。

解决思路一句话:对外只开 80/443,Nginx 按域名(Host 头)把请求分流到不同的后端服务。后端可以是一台机器上的不同端口,也可以是 Docker 容器,甚至可以是另一台内网机器——对访问者完全透明。

反向代理要转发哪些东西

除了基本的 proxy_pass,有三样东西必须跟着走:

location / {
    proxy_pass http://127.0.0.1:8080;            # 转发到后端
    proxy_set_header Host $host;                 # 让后端知道用户访问的是哪个域名
    proxy_set_header X-Real-IP $remote_addr;     # 真实客户端 IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 完整链路
    proxy_set_header X-Forwarded-Proto $scheme;  # 是 http 还是 https
}

坑 1:不传 Host 头,后端按 IP 或默认站点响应,多个站全串台。
坑 2:不传 X-Real-IP,后端日志里全是 127.0.0.1,排错全靠猜。
坑 3:Web 应用开了 HTTPS 却不知道 X-Forwarded-Proto,容易生成 http 开头的链接,甚至登录态失效。

HTTPS:先跳转,再签证书

规范的做法是 80 端口只干两件事:响应 ACME 验证、把其余请求 301 到 https。

server {
    listen 80;
    server_name blog.example.com www.blog.example.com;

    location /.well-known/acme-challenge/ {      # 证书续期验证专用
        root /var/www/acme-challenge;
    }
    location / { return 301 https://blog.example.com$request_uri; }
}

证书用 Let's Encrypt,一条命令签下来:

sudo certbot certonly --webroot -w /var/www/acme-challenge \
    -d blog.example.com -d www.blog.example.com \
    --key-type rsa --agree-tos --email admin@example.com

一次 HTTPS 访问的完整链路

为什么要显式 --key-type rsa 新版 Let's Encrypt 默认签 ECDSA 证书,证书链走到 ISRG Root X2——这个根比较新,部分老 Android 和国产浏览器不认。RSA 证书走的是被 IdenTrust 交叉签名的 ISRG Root X1,几乎所有设备都信任。面向国内访客的场景,宁可要兼容性。

后端端口:宁关勿开

Nginx 反代之后,后端服务(8080 之类的端口)完全不需要暴露到公网。两个手段叠加:

# 手段一:容器端口只绑本机回环
docker run -p 127.0.0.1:8080:80 ...

# 手段二:iptables 对公网来源直接 DROP(以 8080 为例)
iptables -A INPUT -p tcp --dport 8080 -s 0.0.0.0/0 -j DROP
iptables-save > /etc/iptables/rules.v4

对外只有 80/443 两个入口,扫描面小到可以忽略,这是多站一机最重要的安全底线。

上线前的自检清单

# 1. 配置语法 + 平滑重载
sudo nginx -t && sudo systemctl reload nginx

# 2. 绕过 DNS 直接验证本机各站点(排除域名解析干扰)
curl -sk --resolve blog.example.com:443:127.0.0.1 -o /dev/null -w '%{http_code}\n' https://blog.example.com/

# 3. 验证重定向链:www → 主站、http → https
curl -sI http://blog.example.com/ | grep Location

# 4. 验证证书链没断
echo | openssl s_client -connect 127.0.0.1:443 -servername blog.example.com 2>/dev/null \
    | grep "Verify return code"

如果返回 Verify return code: 0 (ok),证书链就是完整的。

两个容易被忽略的 nginx 坑

  • 配置目录里别放 .bak 备份文件。nginx 会把这个目录下所有文件都当配置加载,同名 server_name 的备份文件会导致 "conflicting server name" 警告,而且先加载的那份说了算——你改了半天发现没生效,其实是被备份文件盖住了。备份请放目录外面或改名移走。
  • 同端口 server 块的 listen 参数必须一致。一个写 listen 443 ssl http2;,另一个写 listen 443 ssl;,重载时 nginx 会警告 "protocol options redefined"。要带就都带 http2。

一台普通云服务器、一个公网 IP、多个站点,跑得稳稳的——靠的就是把"入口收口"这件事做干净:证书只在一层、跳转只在一层、暴露面缩到最小。下一篇聊聊这套架构里 Docker 容器数据怎么持久化、怎么备份。