从零在华为云 ECS 上部署 Docker + Nginx 站点并配置 HTTPS:一次踩坑全记录

举报
yd_240094909 发表于 2026/10/23 09:34:37 2026/10/23
【摘要】 一台 2 核 4G 的华为云 ECS,从裸机到跑起带 HTTPS 的静态站点,全程 Docker Compose 管理。包含安全组配置、系统初始化、Nginx 调优、acme.sh 自动续期和 5 个高频坑位。

一台 2 核 4G 的 ECS,从裸机到跑起一个带 HTTPS 的静态站点,全程用 Docker Compose 管理。本文把每一步的命令、配置和踩过的坑都记下来,照着做大概 30 分钟能复现。

写在前面

很多人第一次上华为云 ECS 会卡在几个地方:安全组没放行端口、容器里解析不到域名、证书续期后 Nginx 没重载。这篇文章按「实例 → 系统 → Docker → 站点 → HTTPS」的顺序走一遍,每一步都给出可直接复制的命令,最后单独一节列坑。

环境约定:

  • 系统:Ubuntu 22.04 LTS
  • 规格:2 vCPU / 4 GiB(静态站 + 反向代理完全够用)
  • 部署方式:Docker Compose,Nginx 容器对外,静态文件挂载进容器
  • 证书:acme.sh 申请 Let's Encrypt,自动续期

一、实例与网络:先把路打通

创建 ECS 时有两件事必须在开机前想清楚,否则后面全是玄学问题。

1. 安全组要一次配齐。 入方向至少放行:

端口 协议 来源 用途
22 TCP 你的公网 IP/32 SSH 管理
80 TCP 0.0.0.0/0 HTTP 与证书签发校验
443 TCP 0.0.0.0/0 HTTPS

这里建议 22 端口只放行自己的出口 IP,不要图省事开 0.0.0.0/0,公网上的爆破流量会比你想象的多得多。

2. 带宽按峰值估。 静态站点的瓶颈通常在带宽而不是 CPU。1 Mbps 约等于 128 KB/s,如果你的首页有 1 MB 静态资源,首屏就要 8 秒——这种情况下压缩和 CDN 比升配置更划算。

创建完成后绑定弹性公网 IP(EIP),记下公网地址。

二、系统初始化:把地基打平

第一次登录用 root + 密钥。进去之后第一件事不是装软件,是把 root 密码登录关掉。

# 新建运维用户并授权
adduser deploy
usermod -aG sudo deploy

# 把公钥复制过去
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

# 禁用 root 密码登录与密码认证
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

改完配置先别断开当前会话,另开一个终端用 deploy 账号登录验证成功,再关掉旧连接。这一步能救回不少被锁在门外的服务器。

接着做几项常规设置:

# 时区
timedatectl set-timezone Asia/Shanghai

# 系统更新
apt update && apt -y upgrade

# 2C4G 建议加 2G swap,防止构建镜像时 OOM
fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

三、安装 Docker 与 Compose

用 Docker 官方脚本最省事,它会自动装好 docker compose 插件:

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker

装完先配一下日志轮转。默认情况下容器日志会无限增长,几个月后能把磁盘吃满:

sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "50m", "max-file": "3" },
  "registry-mirrors": ["https://<你的镜像加速地址>"]
}
EOF
sudo systemctl restart docker

验证:

docker --version
docker compose version
docker run --rm hello-world

四、用 Compose 编排 Nginx 与站点

目录结构保持干净,后面扩服务会轻松很多:

/srv/site
├── docker-compose.yml
├── nginx/
│   └── conf.d/
│       └── site.conf
├── html/
│   └── index.html
└── certs/

docker-compose.yml:

services:
  web:
    image: nginx:1.27-alpine
    container_name: site-web
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./html:/usr/share/nginx/html:ro
      - ./certs:/etc/nginx/certs:ro
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3

nginx/conf.d/site.conf,重点在压缩、缓存和安全头:

server {
    listen 80;
    server_name example.com;

    # 证书签发校验用,务必保留
    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html;
    }

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

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL:10m;

    root  /usr/share/nginx/html;
    index index.html;

    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1k;

    # 带指纹的静态资源长缓存
    location ~* \.(js|css|png|jpg|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # HTML 不缓存,保证发版即时生效
    location = /index.html {
        add_header Cache-Control "no-cache";
    }

    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options SAMEORIGIN;
    add_header Referrer-Policy strict-origin-when-cross-origin;

    location = /healthz { return 200 "ok"; }
}

启动:

cd /srv/site
docker compose up -d
docker compose ps
curl -I http://127.0.0.1

五、HTTPS:签发与自动续期

Let's Encrypt 证书 90 天有效期,必须做自动续期,否则第 91 天站点直接挂。

用 webroot 模式签发,不需要停 Nginx:

curl https://get.acme.sh | sh -s email=you@example.com

~/.acme.sh/acme.sh --issue \
  -d example.com -d www.example.com \
  -w /srv/site/html --server letsencrypt

~/.acme.sh/acme.sh --install-cert -d example.com \
  --key-file       /srv/site/certs/privkey.pem \
  --fullchain-file /srv/site/certs/fullchain.pem \
  --reloadcmd      "docker exec site-web nginx -s reload"

--reloadcmd 是关键:acme.sh 续期成功后会自动执行它,让 Nginx 加载新证书。少了这一行,证书文件更新了但 Nginx 还在用旧的,到期那天才会发现。

acme.sh 安装时会自动写入 crontab,检查一下:

crontab -l | grep acme

六、踩坑记录

1. 安全组放行了但还连不上。 除了安全组,Ubuntu 自带的 ufw 也可能拦着。sudo ufw status 看一眼,需要的话 sudo ufw allow 443/tcp。

2. 容器里 ping 不通外网、解析不了域名。 多半是宿主机 DNS 配置的问题,容器继承 /etc/resolv.conf。检查 resolvectl status,必要时在 compose 里显式指定 dns: [223.5.5.5, 1.1.1.1]。

3. 证书续期成功但浏览器仍报过期。 就是上面说的 --reloadcmd 没配。手动补救:docker exec site-web nginx -s reload。

4. docker compose up 报端口被占用。 检查是否装过系统自带的 nginx/apache:ss -lntp | grep :80,有的话 systemctl disable --now nginx。

5. 磁盘写满但 du 看不出来。 容器日志和已删除但仍被进程占用的文件不占 du 的统计。用 df -h 看分区,lsof +L1 找已删除的句柄。

七、成本与收尾

这套配置的资源占用很低:Nginx 容器常驻内存约 10 MB,2C4G 的实例跑一个中小流量站点绰绰有余。真正要留意的是带宽和 EIP 的计费方式——按流量计费适合波峰明显的场景,按带宽计费适合持续有流量的站点,长期跑建议先观察一周的实际出流量再决定。

另外建议把 html/ 目录纳入 Git 管理,用 CI 构建后 rsync 到服务器,比手工传文件可靠得多:

rsync -avz --delete dist/ deploy@<EIP>:/srv/site/html/

小结

整个流程的核心其实就三件事:网络先通、配置集中管理、证书续期要有 reload 钩子。把这三件事做对,剩下的都是 Nginx 配置的细节问题。Docker Compose 的价值在于把「服务器上装了什么」变成一份可以进 Git 的文件,换台机器 up -d 就能复现——这比记住一堆手工命令重要得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。