# NGINX ACME 模块全攻略：从编译安装到自动化证书管理


`ngx_http_acme_module` 是由 NGINX 官方（F5, Inc.）开发的一个基于 Rust 的动态模块。它实现了 ACMEv2 协议（RFC 8555），允许 NGINX 直接处理证书的申请、续期和管理，支持 HTTP-01 和 TLS-ALPN-01 验证方式。

<!--more-->

## 1. 编译与安装

由于该模块通常不包含在预编译的 NGINX 发行版中，我们需要通过源码编译的方式将其作为动态模块集成。

### 前置条件
- **操作系统**：推荐 Debian 或 Ubuntu。
- **工具依赖**：系统需安装有 `curl`, `jq`, `tar` 以及基本的编译环境。
- **权限**：具有 `sudo` 权限。

### 使用自动化脚本编译
你可以创建一个名为 `nginx-acme.sh` 的脚本并运行它。该脚本会自动执行以下流程：
1. 获取 `nginx-acme` 最新版本。
2. 检测系统当前 Nginx 版本并下载匹配的源码。
3. 自动安装必要的编译依赖（Debian/Ubuntu）。
4. 使用 `--with-compat` 参数编译动态模块，确保二进制兼容性。
5. 将 `ngx_http_acme_module.so` 安装到 Nginx 模块目录并验证加载。

脚本内容如下：

```bash
#!/usr/bin/env bash

#============================================================
# File: nginx-acme.sh
# Description: 编译 nginx-acme 模块并集成到当前系统 Nginx
# Author: Jetsung Chan <i@jetsung.com>
# Version: 0.1.0
# CreatedAt: 2026-03-15
# UpdatedAt: 2026-03-15
#============================================================

if [[ -n "${DEBUG:-}" ]]; then
    set -eux
else
    set -euo pipefail
fi

USER_ID="$(id -u)"

sudo_exec() {
    if [[ "$USER_ID" -ne 0 ]]; then
        sudo "$@"
    else
        "$@"
    fi
}

check_is_command() {
    command -v "$1" >/dev/null 2>&1
}

# 1. 获取最新 nginx-acme 版本和下载链接
echo "正在获取 nginx-acme 最新版本信息..."
ACME_RELEASE_INFO=$(curl -fsSL https://api.github.com/repos/nginx/nginx-acme/releases/latest)
ACME_VERSION=$(echo "$ACME_RELEASE_INFO" | jq -r '.tag_name')
ACME_DOWNLOAD_URL=$(echo "$ACME_RELEASE_INFO" | jq -r '.assets[] | select(.name | endswith(".tar.gz")) | .browser_download_url')

echo "最新版本: $ACME_VERSION"

# 2. 获取当前系统 Nginx 版本
if ! check_is_command "nginx"; then
    echo "错误: 系统未安装 nginx，无法编译模块。"
    exit 1
fi

NGINX_VER=$(nginx -v 2>&1 | cut -d '/' -f 2)
echo "当前 Nginx 版本: $NGINX_VER"

# 3. 准备工作目录
WORK_DIR=$(mktemp -d /tmp/nginx-acme-build.XXXXXX)
cd "$WORK_DIR"

# 4. 下载源码
echo "正在下载 nginx-acme 源码..."
curl -fsSL "$ACME_DOWNLOAD_URL" -o "nginx-acme.tar.gz"
tar -xzf "nginx-acme.tar.gz"
ACME_SRC_DIR=$(find . -maxdepth 1 -type d -name "nginx-acme-*" | head -n 1)

echo "正在下载 Nginx $NGINX_VER 源码..."
curl -fsSL "https://nginx.org/download/nginx-${NGINX_VER}.tar.gz" -o "nginx.tar.gz"
tar -xzf "nginx.tar.gz"
NGINX_SRC_DIR="nginx-${NGINX_VER}"

# 5. 安装编译依赖
echo "正在安装编译依赖..."
sudo_exec apt-get update -y
sudo_exec apt-get install -y clang pkg-config libssl-dev libpcre2-dev zlib1g-dev libclang-dev

# 6. 编译模块
echo "开始编译模块..."
cd "$NGINX_SRC_DIR"

# 获取原始编译参数并添加动态模块支持
# 注意：必须包含 --with-compat 以确保二进制兼容
./configure --with-compat --with-http_ssl_module --add-dynamic-module="../$ACME_SRC_DIR"

make modules

# 7. 安装模块
MODULE_PATH=$(nginx -V 2>&1 | grep -oP "modules-path=\K[^ ]*")
if [[ -z "$MODULE_PATH" ]]; then
    MODULE_PATH="/usr/lib/nginx/modules"
fi

echo "编译完成。正在将模块复制到 $MODULE_PATH..."
sudo_exec mkdir -p "$MODULE_PATH"
sudo_exec cp objs/ngx_http_acme_module.so "$MODULE_PATH/"

# 8. 测试模块是否正常加载
echo "正在验证模块是否加载正常..."
TEST_CONF=$(mktemp /tmp/nginx-acme-test.XXXXXX.conf)
cat > "$TEST_CONF" <<EOF
error_log /tmp/nginx-acme-test-error.log;
pid /tmp/nginx-acme-test.pid;
load_module $MODULE_PATH/ngx_http_acme_module.so;
events {
    worker_connections 1024;
}
http {
    access_log /tmp/nginx-acme-test-access.log;
}
EOF

if sudo_exec nginx -t -c "$TEST_CONF" > /dev/null 2>&1; then
    echo "验证成功: 模块 $MODULE_PATH/ngx_http_acme_module.so 已成功加载并与当前 Nginx 兼容。"
else
    echo "验证失败: 模块加载出错。请检查错误日志: /tmp/nginx-acme-test-error.log"
    sudo_exec cat /tmp/nginx-acme-test-error.log
    rm -f "$TEST_CONF"
    exit 1
fi

rm -f "$TEST_CONF" /tmp/nginx-acme-test-error.log /tmp/nginx-acme-test.pid /tmp/nginx-acme-test-access.log

echo "----------------------------------------------------"
echo "nginx-acme 模块已安装并验证成功！"
echo "模块位置: $MODULE_PATH/ngx_http_acme_module.so"
echo "请在您的主 nginx.conf 顶层添加以下行以启用模块:"
echo "load_module modules/ngx_http_acme_module.so;"
echo "----------------------------------------------------"

# 清理
rm -rf "$WORK_DIR"
```

赋予执行权限并运行：

```bash
chmod +x nginx-acme.sh
./nginx-acme.sh
```
若看到“nginx-acme 模块已安装并验证成功！”的提示，说明安装已完成。

---

## 2. 模块加载

在 NGINX 中使用动态模块，必须在 `nginx.conf` 的**最顶部**（全局块）进行加载：

```nginx
load_module modules/ngx_http_acme_module.so;
```

---

## 3. 基础配置示例

以下是一个使用 Let's Encrypt 获取证书并在 443 端口使用的典型配置：

```nginx
http {
    # 1. 必须配置解析器，用于解析 ACME 服务器域名
    resolver 8.8.8.8 1.1.1.1 valid=300s;
    resolver_timeout 5s;

    # 2. 定义共享内存区域（必填），用于同步证书状态
    # 1M 空间约可管理 50 个证书
    acme_shared_zone zone=ngx_acme_shared:1M;

    # 3. 定义 ACME 发行者
    acme_issuer letsencrypt {
        uri https://acme-v02.api.letsencrypt.org/directory;
        state_path /var/cache/nginx/acme; # 存储账户和证书的目录
        accept_terms_of_service;
        contact mailto:admin@example.com;
    }

    # 4. HTTP 验证 Server (80 端口)
    server {
        listen 80;
        server_name example.com;

        # 模块会自动处理 /.well-known/acme-challenge/
        location / {
            return 301 https://$host$request_uri;
        }
    }

    # 5. HTTPS Server (443 端口)
    server {
        listen 443 ssl;
        server_name example.com;

        # 关联发行者并请求证书
        acme_certificate letsencrypt;

        # 使用模块提供的动态变量加载证书和密钥
        ssl_certificate     $acme_certificate;
        ssl_certificate_key $acme_certificate_key;

        # 推荐：启用证书缓存
        ssl_certificate_cache max=10;

        location / {
            root /usr/share/nginx/html;
        }
    }
}
```

---

## 4. 指令详解

| 指令 | 上下文 | 说明 |
| :--- | :--- | :--- |
| **`acme_issuer`** | `http` | 定义证书发行者块。内含 `uri` (必填)、`contact`、`state_path` 等子指令。 |
| **`acme_certificate`** | `server` | 为当前虚拟主机请求证书。格式：`acme_certificate issuer_name [identifiers...];` |
| **`acme_shared_zone`** | `http` | 分配共享内存以在所有工作进程间同步证书数据。 |
| **`state_path`** | `acme_issuer` | 证书和账户数据的持久化存储路径。**必须对 Nginx 用户可见并有写权限。** |

---

## 5. 进阶用法

### TLS-ALPN-01 验证
如果你无法开启 80 端口，可以使用 TLS-ALPN-01 方式（必须在 443 端口进行）：

```nginx
acme_issuer cloud_issuer {
    uri https://...;
    challenge tls-alpn-01;
    state_path /etc/nginx/acme;
}

server {
    listen 443 ssl;
    server_name example.com;
    acme_certificate cloud_issuer;
    ...
}
```

### IP 证书支持
该模块支持根据 RFC 8738 申请 IP 证书。只需将 IP 地址放入 `server_name` 或作为 `acme_certificate` 的参数即可。

### 嵌入式变量
模块提供了两个核心变量，直接用于 `ssl_certificate` 系列指令：
- `$acme_certificate`: 签发后的证书内容（PEM 格式）。
- `$acme_certificate_key`: 对应的私钥内容。

---

## 6. 注意事项与维护

### 目录权限
在启动前，请务必手动创建状态目录并授予 Nginx 运行用户（如 `www-data`）读写权限：
```bash
sudo mkdir -p /var/cache/nginx/acme
sudo chown www-data:www-data /var/cache/nginx/acme
```

### 域名解析
必须在 `http` 块中配置 `resolver`，否则模块无法连接到 ACME 发行者服务器。

### 验证配置与重启
完成修改后，请先测试配置：
```bash
sudo nginx -t
sudo systemctl reload nginx
```
Nginx 会自动启动后台进程开始与 ACME 服务器通信。你可以查看 Nginx 错误日志以追踪申请进度。

### 故障排查
- **验证失败**：检查 80 端口是否被防火墙拦截，或者是否存在强制性的全局 301 跳转干扰了验证路径。
- **二进制不兼容**：如果手动编译后无法加载，请确保编译环境的 Nginx 源码版本与系统安装的版本完全一致。

---

## 7. 相关资源
- **模块仓库**：[nginx-acme GitHub](https://github.com/nginx/nginx-acme)
- **官方文档**：[ngx_http_acme_module Docs](http://nginx.org/en/docs/http/ngx_http_acme_module.html)

