引言

花了一个小时,从零搭建了这个博客站点。这篇文章记录完整流程——从域名购买到 HTTPS 配置,再到自动化部署。

以后自己回顾,也分享给需要的朋友。

环境概览

开始之前,先看一下要用到的资源:

| 项目 | 说明 ||------|------|| 域名 | 阿里云购买 || 服务器 | 阿里云 ECS(Alibaba Cloud Linux 3) || 架构 | Git Push → 服务器自动构建 → Nginx 静态站点 || SSH 工具 | 任意 SSH 客户端 |

整个搭建的核心思路:把复杂度收敛到服务端构建脚本,让日常使用简单到一行 git push

一、SSH 密钥配置

首先生成本地 SSH 密钥对,把公钥传到服务器。之后连接不用每次输密码。

生成密钥

# 生成 ed25519 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_petthinker

# 把公钥传到服务器
ssh-copy-id -i ~/.ssh/id_ed25519_petthinker root@your-server-ip

设置别名

~/.ssh/config 中添加配置,之后直接 ssh petthinker 就能连接:

Host petthinker
    HostName your-server-ip
    User root
    IdentityFile ~/.ssh/id_ed25519_petthinker

二、安装 Nginx

Alibaba Cloud Linux 用 dnf 直接安装。

安装与启动

# 安装 Nginx
ssh petthinker "dnf install -y nginx"

# 启动服务
ssh petthinker "systemctl start nginx"

常见踩坑:端口冲突

80 端口被旧的 Docker 容器占用,Nginx 无法启动的情况很常见。先检查再清理:

# 检查 80 端口占用
ssh petthinker "ss -tlnp | grep :80"

# 如果被 Docker 容器占用,停掉并删除
ssh petthinker "docker stop zlmediakit && docker rm zlmediakit"

# 重新启动 Nginx
ssh petthinker "systemctl start nginx"

养成习惯:安装服务前先用 ss -tlnp 扫一遍端口。

三、配置 HTTPS

用 Let's Encrypt 自动获取免费 SSL 证书。

获取证书

# 安装 certbot
ssh petthinker "dnf install -y certbot python3-certbot-nginx"

# 自动获取并配置证书
ssh petthinker "certbot --nginx -d yourdomain.com --non-interactive --agree-tos -m admin@yourdomain.com --redirect"

验证自动续期

证书有效期 90 天,certbot 会自动续期。确认续期任务已注册:

# 检查自动续期定时器
ssh petthinker "systemctl status certbot-renew.timer"

输出中看到 Active: active (waiting) 就说明没问题。

四、搭建文章发布系统

这是核心——选择什么方式发布文章?

方案对比

| 方案 | 优点 | 缺点 ||------|------|------|| WordPress | 功能全,生态丰富 | 重,需要数据库,维护成本高 || Hexo / Hugo | 静态站,速度快 | 需要本地环境,部署步骤多 || Git Push + 静态构建 | 极简,一行命令发布 | 需要自己写构建脚本 |

最终选择了第三种:本地用 Markdown 写文章,Git Push 触发自动构建,30 秒内网站更新。

整体架构

本地 ~/blog/posts/*.md(Markdown 写文章)
    ↓ git push
服务器 /var/repos/blog.git(裸仓库)
    ↓ post-receive hook 触发
构建脚本 /usr/local/bin/build-blog.py(Markdown → HTML)
    ↓ 输出
Nginx 静态站点 /var/www/blog/
    ↓ 用户访问
https://yourdomain.com

创建裸仓库

# 在服务器上创建 Git 裸仓库
ssh petthinker "mkdir -p /var/repos/blog.git && cd /var/repos/blog.git && git init --bare"

编写 post-receive hook

每次 push 自动触发的脚本:

cat << 'EOF' | ssh petthinker "cat > /var/repos/blog.git/hooks/post-receive"
#!/bin/bash
# 每次 git push 触发:拉取文章 → 构建 HTML
GIT_WORK_TREE=/var/www/blog-src git checkout -f master
python3.8 /usr/local/bin/build-blog.py /var/www/blog-src/posts /var/www/blog
EOF

# 授予执行权限
ssh petthinker "chmod +x /var/repos/blog.git/hooks/post-receive"

构建脚本

Python 脚本负责将 Markdown 文件渲染为 HTML 页面:

# build-blog.py — Markdown 文章 → HTML 页面
import markdown, os, sys
from datetime import datetime

POSTS_DIR = sys.argv[1]   # Markdown 源目录
OUT_DIR = sys.argv[2]     # HTML 输出目录

STYLE = """
/* 百度风格浅色主题 — 主题色 #4e6ef2 */
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif;
       max-width: 780px; margin: 0 auto; padding: 24px;
       background: #f5f6f7; color: #222; }
h1 { color: #4e6ef2; border-left: 3px solid #4e6ef2; padding-left: 12px; }
pre { background: #1e1f24; color: #c9d1d9; padding: 16px; border-radius: 6px; }
a { color: #4e6ef2; }
"""

def page(title, body, date=""):
    return f"""<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8">
<title>{title}</title><style>{STYLE}</style></head>
<body>
<nav><a href="/">← 首页</a></nav>
<h1>{title}</h1>
{"<p><time>" + date + "</time></p>" if date else ""}
{body}
<hr><footer>湘ICP备2023025875号-2</footer>
</body></html>"""

# 遍历 posts/*.md 生成 HTML
for f in sorted(os.listdir(POSTS_DIR)):
    if not f.endswith('.md'): continue
    with open(os.path.join(POSTS_DIR, f)) as fp:
        md = fp.read()
    # 解析 front matter(--- 包裹的元数据)
    title = f.replace('.md', '')
    date = datetime.now().strftime('%Y-%m-%d')
    if md.startswith('---'):
        _, fm, body = md.split('---', 2)
        for line in fm.strip().split('\n'):
            if ':' in line:
                k, v = line.split(':', 1)
                if k.strip() == 'title': title = v.strip()
                if k.strip() == 'date': date = v.strip()
    else:
        body = md
    html = page(title, markdown.markdown(body, extensions=['fenced_code', 'tables']), date)
    out_name = os.path.join(OUT_DIR, f.replace('.md', '.html'))
    with open(out_name, 'w') as fp:
        fp.write(html)

部署构建脚本到服务器:

# 上传构建脚本
scp build-blog.py petthinker:/usr/local/bin/build-blog.py

本地 Git 配置

# 初始化本地仓库并关联远程
cd ~/blog
git init
git remote add origin root@your-server-ip:/var/repos/blog.git
git config core.sshCommand "ssh -i ~/.ssh/id_ed25519_petthinker"

发布文章

# 1. 写文章
vim posts/new-article.md

# 2. 提交并推送
git add posts/
git commit -m "新文章:文章标题"

# 3. 推送即发布
git push origin master

推送后 30 秒内网站自动更新。不需要登录服务器,不需要手动构建——这就是选择 Git Push 方案的最大收益。

五、Nginx 配置要点

站点配置文件 /etc/nginx/conf.d/yourdomain.com.conf

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    root /var/www/blog;
    index index.html;

    location / {
        try_files $uri $uri.html $uri/ =404;
    }
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

修改后重载

# 先测试配置语法,通过后再重载
ssh petthinker "nginx -t && systemctl reload nginx"

最终效果

搭建完成后的效果:

  • HTTPS 绿色小锁 + 自动续期
  • 本地 Markdown 写文章,Git Push 发布
  • 30 秒内全站自动更新
  • 零数据库,纯静态 HTML,零维护
  • 百度风格浅色主题(#4e6ef2)

结语

| 坑 | 解法 ||----|------|| 端口冲突 Nginx 无法启动 | 先 ss -tlnp 检查端口占用 || Python 版本太老 | dnf install python38 || HTTPS 证书过期 | certbot 自动续期,systemctl status certbot-renew.timer 确认 || 文章发布麻烦 | Git Push + post-receive hook 自动化 || Nginx 配置报错 | 改配置后先 nginx -t 检查语法再 reload |

整个搭建的核心思路:把复杂度收敛到一处(构建脚本),让日常使用简单到一行命令。