Ubuntu Nginx站点部署实录:从配置详解到自动化脚本

前言
最近为了把跑在内网 8090 端口的博客服务发布到公网,我折腾了一番 Nginx。
目标很简单:通过域名 blog.twopair.cn 访问,由 HTTP 自动跳转 HTTPS,且以后要能轻松管理多个站点。
这里的记录涵盖了从环境安装、配置文件原理详解,到最终封装自动化脚本的全过程。

一、 起步:环境与基础

在 Ubuntu 20.04/22.04 上,第一步永远是把环境铺好。

# 1. 更新源并安装 Nginx 和解压工具
sudo apt update
sudo apt install nginx unzip -y

# 2. 确认防火墙放行 HTTP/HTTPS,否则外网白搭
sudo ufw allow 'Nginx Full'


二、 核心逻辑:Nginx 的“仓库”与“柜台”

Ubuntu 下 Nginx 的目录结构设计非常巧妙,理解这个逻辑是手动配置的基础:

  • /etc/nginx/sites-available/ (仓库)
    存放配置文件的原稿。写在这里的文件 Nginx 是不读的,相当于“草稿箱”。
  • /etc/nginx/sites-enabled/ (柜台)
    存放指向仓库文件的软链接(快捷方式)。Nginx 启动时只加载这里的内容。

我的管理逻辑

  • 上线:用 ln -s 把仓库里的文件链接到柜台。
  • 下线:用 rm 删掉柜台里的链接(原稿保留,随时可恢复)。

三、 深度解析:Nginx 配置文件写了什么?

在使用脚本“一键生成”之前,必须搞懂配置文件里每一行的含义。以我的 blog.twopair.cn 为例,一个标准的HTTPS + 反向代理配置如下:

1. 证书存放规范

为了不乱,我统一将证书存放在 /etc/nginx/cert/<域名>/ 目录下:

  • .pem / .crt:公钥/证书链。
  • .key:私钥(注意权限保护)。

2. 配置文件拆解

通常包含两个 server 块。

第一部分:HTTP 强制跳转 HTTPS

拦截所有 80 端口请求,将其扔给 443 端口。

server {
    listen 80;
    server_name blog.twopair.cn; # 匹配我的域名

    # 301 永久重定向到 HTTPS,保留原本的请求路径
    return 301 https://$host$request_uri;
}

第二部分:HTTPS 核心配置

server {
    # --- 基础监听 ---
    listen 443 ssl;
    server_name blog.twopair.cn;

    # --- SSL 证书配置 ---
    # 这里指向我解压后的证书路径
    ssl_certificate     /etc/nginx/cert/blog.twopair.cn/fullchain.pem;
    ssl_certificate_key /etc/nginx/cert/blog.twopair.cn/privkey.key;

    # --- 关键:解决上传报错 ---
    # 默认 Nginx 限制上传 1MB,博客传大图会报 413 Too Large
    # 手动改成了 100M
    client_max_body_size 100M; 

    # --- 反向代理设置 (连接后端) ---
    location / {
        proxy_pass http://127.0.0.1:8090; # 转发给本地 8090 端口

        # --- 请求头传递 (Header) ---
        # 如果不加下面这几行,后端服务拿到的 IP 全是 127.0.0.1
        # 这会导致日志分析和 IP 限制功能失效
        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;       # 告诉后端原本是 https
    }
}


四、 终极方案:自动化管理脚本

为了以后不再手动复制粘贴上述配置,我让AI写了一个脚本 vhost_manager.sh
它会自动部署站点:解压证书 -> 生成上述配置 -> 建立软链接 -> 重载 Nginx

1. 脚本代码

保存为 vhost_manager.sh 并赋予执行权限 chmod +x vhost_manager.sh

#!/bin/bash
# Nginx 站点管理工具

set -euo pipefail

NGINX_AVAILABLE="/etc/nginx/sites-available"
NGINX_ENABLED="/etc/nginx/sites-enabled"
CERT_BASE="/etc/nginx/cert"

error() {
    echo "错误:$1" >&2
    exit 1
}

checkRoot() {
    if [ "$(id -u)" -ne 0 ]; then
        error "请使用 root 或 sudo 执行该脚本"
    fi
}

reloadNginx() {
    nginx -t || error "Nginx 配置校验失败"
    systemctl reload nginx
}

case "${1:-}" in
    deploy)
        checkRoot

        DOMAIN="${2:-}"
        PORT="${3:-}"
        ZIP_PATH="${4:-}"

        if [ -z "$DOMAIN" ] || [ -z "$PORT" ] || [ -z "$ZIP_PATH" ]; then
            error "用法: $0 deploy <domain> <port> <cert.zip>"
        fi

        if ! [[ "$PORT" =~ ^[0-9]+$ ]]; then
            error "端口号必须是数字"
        fi

        if [ ! -f "$ZIP_PATH" ]; then
            error "证书 ZIP 文件不存在: $ZIP_PATH"
        fi

        CERT_DIR="$CERT_BASE/$DOMAIN"
        CONF_PATH="$NGINX_AVAILABLE/$DOMAIN"

        echo ">>> [1/5] 准备证书目录: $CERT_DIR"
        mkdir -p "$CERT_DIR"

        unzip -j -o "$ZIP_PATH" -d "$CERT_DIR" > /dev/null \
            || error "证书 ZIP 解压失败"

        PEM=$(find "$CERT_DIR" \( -name "*.pem" -o -name "*.crt" \) -type f | head -n 1)
        KEY=$(find "$CERT_DIR" -name "*.key" -type f | head -n 1)

        if [ -z "$PEM" ] || [ -z "$KEY" ]; then
            error "未在证书包中找到 .pem/.crt 或 .key 文件"
        fi

        chmod 600 "$KEY"
        chmod 644 "$PEM"

        echo ">>> [2/5] 生成 Nginx 配置: $CONF_PATH"

        cat > "$CONF_PATH" <<EOF
server {
    listen 80;
    server_name $DOMAIN;
    return 301 https://\$host\$request_uri;
}

server {
    listen 443 ssl http2;
    server_name $DOMAIN;

    ssl_certificate $PEM;
    ssl_certificate_key $KEY;

    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:$PORT;
        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_connect_timeout 60s;
        proxy_read_timeout 300s;
    }
}
EOF

        echo ">>> [3/5] 启用站点"
        ln -sf "$CONF_PATH" "$NGINX_ENABLED/$DOMAIN"

        echo ">>> [4/5] 校验并重载 Nginx"
        reloadNginx

        echo ">>> [5/5] 完成"
        echo ">>> https://$DOMAIN -> 127.0.0.1:$PORT"
        ;;

    enable)
        checkRoot
        DOMAIN="${2:-}"
        [ -z "$DOMAIN" ] && error "用法: $0 enable <domain>"

        ln -sf "$NGINX_AVAILABLE/$DOMAIN" "$NGINX_ENABLED/$DOMAIN"
        reloadNginx
        echo ">>> 站点 $DOMAIN 已上线"
        ;;

    disable)
        checkRoot
        DOMAIN="${2:-}"
        [ -z "$DOMAIN" ] && error "用法: $0 disable <domain>"

        rm -f "$NGINX_ENABLED/$DOMAIN"
        reloadNginx
        echo ">>> 站点 $DOMAIN 已下线(配置保留)"
        ;;

    remove)
        checkRoot
        DOMAIN="${2:-}"
        [ -z "$DOMAIN" ] && error "用法: $0 remove <domain>"

        rm -f "$NGINX_ENABLED/$DOMAIN"
        rm -f "$NGINX_AVAILABLE/$DOMAIN"
        rm -rf "$CERT_BASE/$DOMAIN"

        reloadNginx
        echo ">>> 站点 $DOMAIN 已彻底移除"
        ;;

    status)
        echo "--- 已启用站点 ---"
        ls -l "$NGINX_ENABLED" || echo "暂无已启用站点"
        ;;

    *)
        echo "用法:"
        echo "  $0 deploy  <domain> <port> <cert.zip>"
        echo "  $0 enable  <domain>"
        echo "  $0 disable <domain>"
        echo "  $0 remove  <domain>"
        echo "  $0 status"
        exit 1
        ;;
esac


五、 实战演示:我的博客部署过程

假设我已经把申请好的 SSL 证书压缩包 blog_cert.zip 上传到了服务器,且博客程序已经在 8090 端口运行。

1. 一键部署

运行脚本,填入我的域名和端口:

./vhost_manager.sh deploy blog.twopair.cn 8090 ./blog_cert.zip

执行后,脚本会自动解压证书、写入上面的那套配置、并重载Nginx部署站点。

2. 日常维护

  • 临时维护博客
    ./vhost_manager.sh disable blog.twopair.cn
    (此时用户访问会挂掉,但配置和证书都在)
  • 维护完毕上线
    ./vhost_manager.sh enable blog.twopair.cn
  • 以后不再用这个站了
    ./vhost_manager.sh remove blog.twopair.cn
    (彻底删除干净)

六、 避坑指南

在折腾过程中遇到过几个坑,特此记录:

  1. 413 Request Entity Too Large
  • 现象:博客上传图片稍微大点就报错。
  • 原因:Nginx 默认限制 1MB。
  • 解决:脚本里我已经加了 client_max_body_size 100M;