跳到内容
Caiden's Blog
返回

Docker Compose 部署 Nginx 并配置 SSL 自动续期

用 Docker Compose 跑 Nginx 是目前比较省心的部署方式,配置文件管容器参数,目录挂载管持久化,容器删了数据还在。这篇从零开始,把 Nginx 部署、反向代理、HTTPS 证书申请和自动续期整个流程走一遍,顺便记录几个容易踩的坑。

创建目录结构

在服务器上建一个专属目录,把 Nginx 相关的东西都放进去:

mkdir -p nginx-docker/{html,conf.d,logs,certs}
cd nginx-docker

目录结构大概是这样:

nginx-docker/
├── conf.d/            # 站点配置文件
├── html/              # 静态网页
├── logs/              # Nginx 日志
├── certs/             # SSL 证书(后面用)
└── docker-compose.yml

编写 docker-compose.yml

services:
  nginx:
    image: nginx:latest
    container_name: my-nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    extra_hosts:
      - "host.docker.internal:host-gateway"  # 让容器能访问宿主机
    volumes:
      - ./html:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d
      - ./logs:/var/log/nginx
      - ./certs:/etc/nginx/certs
    environment:
      - TZ=Asia/Shanghai

这里几个要点:restart: always 保证容器挂了自动重启、开机自启;extra_hosts 那行很关键,它让 Nginx 容器能通过 host.docker.internal 访问宿主机上的服务,后面反向代理全靠它;certs 目录先挂上,后面申请证书时直接往里放文件就行。

启动容器:

docker compose up -d

写个维护脚本

每次手动敲 docker exec 命令太麻烦,写个脚本封装常用操作:

#!/bin/bash

CONTAINER_NAME="my-nginx"

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

show_help() {
    echo -e "${GREEN}Nginx 容器维护脚本${NC}"
    echo "用法: ./nginx-manage.sh [命令]"
    echo ""
    echo "可用命令:"
    echo "  start   - 启动容器"
    echo "  stop    - 停止容器"
    echo "  down    - 停止并删除容器"
    echo "  test    - 测试配置文件语法"
    echo "  reload  - 平滑重载配置"
    echo "  logs    - 查看日志"
    echo "  status  - 查看运行状态"
}

if [ -z "$1" ]; then
    show_help
    exit 1
fi

check_compose_file() {
    if [ ! -f "docker-compose.yml" ] && [ ! -f "docker-compose.yaml" ]; then
        echo -e "${RED}错误: 当前目录找不到 docker-compose.yml${NC}"
        exit 1
    fi
}

case "$1" in
    start)
        check_compose_file
        docker compose up -d
        echo -e "${GREEN}容器启动完成${NC}"
        ;;
    stop)
        check_compose_file
        docker compose stop
        ;;
    down)
        check_compose_file
        docker compose down
        ;;
    test)
        docker exec "$CONTAINER_NAME" nginx -t
        ;;
    reload)
        if docker exec "$CONTAINER_NAME" nginx -t; then
            docker exec "$CONTAINER_NAME" nginx -s reload
            echo -e "${GREEN}配置重载完成${NC}"
        else
            echo -e "${RED}配置文件有语法错误,放弃重载${NC}"
        fi
        ;;
    logs)
        check_compose_file
        docker compose logs -f nginx
        ;;
    status)
        docker ps -f name="$CONTAINER_NAME"
        ;;
    *)
        echo -e "${RED}未知命令: $1${NC}"
        show_help
        exit 1
        ;;
esac

保存后给执行权限:

chmod +x nginx-manage.sh

以后修改了配置文件,跑 ./nginx-manage.sh reload 就行,脚本会先测语法再重载,不用担心写错配置把服务搞挂。

配置反向代理

假设有个服务跑在宿主机的 127.0.0.1:41373,想通过 news.caiden.asia 这个域名访问。

conf.d 目录下新建 news.caiden.asia.conf

server {
    listen 80;
    server_name news.caiden.asia;

    location / {
        proxy_pass http://host.docker.internal:41373;
        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_pass 里写的是 host.docker.internal 而不是 127.0.0.1,因为 127.0.0.1 在容器里指的是容器自己,不是宿主机。

重载配置:

./nginx-manage.sh reload

确认域名的 A 记录解析到服务器 IP,防火墙放行 80 端口,就能通过域名访问了。

配置 SSL 证书

acme.sh 申请 Let’s Encrypt 的免费证书,到期前自动续期,基本免维护。

安装 acme.sh

curl https://get.acme.sh | sh
source ~/.bashrc
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

修改 Nginx 配置,放行验证请求

Let’s Encrypt 发证书前会访问 http://域名/.well-known/acme-challenge/xxx 来验证域名所有权。在 Nginx 配置里加个 location 处理这个请求:

server {
    listen 80;
    server_name news.caiden.asia;

    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html;
    }

    location / {
        proxy_pass http://host.docker.internal:41373;
        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";
    }
}

重载配置让规则生效:

./nginx-manage.sh reload

申请证书

~/.acme.sh/acme.sh --issue -d news.caiden.asia -w ~/apps/nginx/html

看到绿色的 Cert success! 就说明申请成功了。

安装证书并设置自动续期

这一步把证书复制到 certs 目录,同时告诉 acme.sh 续期后自动重载 Nginx:

~/.acme.sh/acme.sh --install-cert -d news.caiden.asia \
--key-file       /home/ubuntu/apps/nginx/certs/news.caiden.asia.key  \
--fullchain-file /home/ubuntu/apps/nginx/certs/news.caiden.asia.cer \
--reloadcmd     "sudo docker exec my-nginx nginx -s reload"

以后证书快过期时,acme.sh 的后台定时任务会自动续期,续完自动执行 reloadcmd 重载 Nginx,全程不用人工干预。

启用 HTTPS

最后一次修改 Nginx 配置,把 HTTP 跳转到 HTTPS:

server {
    listen 80;
    server_name news.caiden.asia;

    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name news.caiden.asia;

    ssl_certificate /etc/nginx/certs/news.caiden.asia.cer;
    ssl_certificate_key /etc/nginx/certs/news.caiden.asia.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://host.docker.internal:41373;
        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";
    }
}

重载配置:

./nginx-manage.sh reload

访问 https://news.caiden.asia,浏览器地址栏会显示安全锁,证书受全球浏览器信任。

踩坑记录

Ubuntu 下用 sh 跑 bash 脚本报语法错误。 Ubuntu 的 sh 默认指向 dash,不支持 function show_help() { ... } 这种写法。要么用 bash nginx-manage.sh 执行,要么把函数定义改成 show_help() { ... }(去掉 function 关键字)。

acme.sh 不要加 sudo 执行。 acme.sh 装在当前用户目录下,加了 sudo 会去 /root 找配置文件,找不到就报错。先确保 certs 目录权限正确:sudo chown -R $USER:$USER /home/ubuntu/apps/nginx/certs,然后去掉命令前面的 sudo 执行。注意 --reloadcmd 里面的 sudo 要保留,不然定时任务没权限重启容器。


分享到:

下一篇
PaddleOCR v5 Windows 安装与使用