本文记录一套只作用于单个站点的 Nginx 访问限制配置。

目标如下:

  • 允许指定 CDN 域名访问
  • 允许指定 IP 访问
  • 其他请求直接返回 403
  • 不影响 Let's Encrypt 自动续期

一、http 块新增映射

先在 nginx.confhttp 块加入域名和 IP 的白名单映射。

....
http {
    # 取 CDN 回源时的原始 Host
    map $http_x_forwarded_host $original_host {
        default $http_x_forwarded_host;
        ''      $host;
    }

    # 域名白名单
    map $original_host $allowed_domain {
        default 0;
        include /www/wwwroot/limit/domain_map.conf;
    }

    # IP 白名单
    geo $remote_addr $allowed_ip {
        default 0;
        include /www/wwwroot/limit/allow_ips_geo.conf;
    }
....
}

这样后续增删域名和 IP 时,只需要改独立文件,不用反复修改主配置。

二、站点配置中的修改位置

把这段加在目标站点 server 块里,建议放在 root 后面、其他访问规则之前:

  ....
server {
    index index.php index.html index.htm;
    root /www/wwwroot/example-site/public;

    # 证书验证路径 - 必须在访问控制之前,允许所有访问
    location ~ \.well-known/acme-challenge {
        allow all;
        default_type "text/plain";
    }

    #访问控制-START
    set $access_allowed 0;

    # 检查域名白名单
    if ($allowed_domain = 1) {
        set $access_allowed 1;
    }

    # 检查IP白名单
    if ($allowed_ip = 1) {
        set $access_allowed 1;
    }

    # 证书验证路径豁免
    if ($uri ~* "^/\.well-known/acme-challenge/") {
        set $access_allowed 1;
    }

    if ($access_allowed = 0) {
        return 403;
    }
    #访问控制-END

    include /www/server/panel/vhost/nginx/extension/www.example.com/*.conf;
  ....
}

三、domain_map.conf 的匹配方式

这里用的是 map 的匹配方式,常用写法有两类:

  • 精确匹配
  • 正则匹配

实际使用中,最常见的一类写法是:

^(([a-zA-Z0-9-]+\.)*)?example\.com$ 1;

属于正则匹配,含义是:

  • 匹配任意一级子域名和根域名

另外几种常见用法示例如下:

# 匹配所有子域名,不匹配根域名
~*\.example\.com$ 1;

# 匹配根域名
example.com 1;

# 匹配固定子域名
cdn.example.com 1;

# 匹配带前缀的子域名
~*^img-[a-z0-9]+\.example\.com$ 1;

# 只匹配任意一级子域名
~*^[^.]+\.example\.com$ 1;

一个更完整的 domain_map.conf 示例:

~*\.example\.com$ 1;
example.com 1;
cdn.example.com 1;
~*^img-[a-z0-9]+\.example\.com$ 1;
~*^[^.]+\.static\.example\.com$ 1;

如果回源域名不止一个,继续往这个文件追加即可。

四、allow_ips_geo.conf 示例

格式:

# 单个 IP
198.51.100.21 1;
198.51.100.34 1;

# 一个网段
203.0.113.0/24 1;

如果只需要放行少量固定 IP,直接一行一个即可;如果是一整段出口地址,直接写 CIDR 会更方便。

五、应用配置

修改完成后执行:

nginx -t
nginx -s reload

如果 nginx -t 没通过,先修正报错,再 reload。

六、关键点

这套写法的重点有两个:

  • 域名和 IP 白名单拆到独立文件中维护
  • /.well-known/acme-challenge/ 必须在访问控制之前放行,否则证书自动续期会失败