
一、面试现场一道看似简单实则暗藏玄机的题想象一下京东一面的场景面试官看完你的简历后突然抛出这样一个问题“我们的某个业务只面向国内用户现在要你用 Nginx 把国外 IP 全部禁止访问你会怎么做能想到几种方案它们之间有什么区别”很多候选人听到这个问题第一反应是查“中国 IP 段”然后写一大堆allow和deny。这个方向不能说错但如果只是这样回答基本就“凉凉”了。因为这道题本质上考察的是几个层次是否理解 Nginx 的访问控制机制allow/deny、geo、map、if的适用场景和优先级。是否了解 GeoIP 生态GeoIP 库、GeoLite2 数据库、ngx_http_geoip_module与ngx_http_geoip2_module的区别。是否考虑性能与维护成本规则数量、匹配效率、数据库更新方案、CDN 场景下的真实客户端 IP。是否具备工程化思维除了 Nginx还会想到 WAF、云厂商安全组、iptables、OpenResty 等组合方案。本文将以这道面试题为切入点从原理、配置文件、实战命令、性能优化到面试表达完整拆解“如何用 Nginx 禁止国外 IP 访问网站”。全文约两万字建议配合本地环境动手实践。二、先建立正确的认知为什么“禁止国外 IP”不是一道简单防火墙题很多初级工程师会把“禁止国外 IP”理解成“维护一份中国 IP 白名单然后允许白名单、拒绝其他”。这个思路在功能上是可行的但在面试和生产环境中它存在几个明显问题。2.1 维护 IP 白名单的成本极高全球 IP 地址由五大区域互联网注册机构RIR分配中国的 IP 段由 CNNIC 和 APNIC 管理。随着运营商扩容、IPv6 普及、云厂商新增机房IP 段会不断变化。如果你手工维护一份静态列表很快就会发现列表会逐渐失效误伤合法用户列表会越来越大Nginx 每次请求都要做大量匹配拖慢响应IPv6 地址数量庞大手工维护几乎不可行。2.2 真实客户端 IP 可能被代理“洗掉”如果你的 Nginx 架设在 CDN、负载均衡器、反向代理之后那么在 Nginx 眼里所有请求的来源 IP 都是 CDN 节点的 IP而不是真实用户 IP。这时直接用$remote_addr做判断会导致所有用户都被误判为“国外”或“国内”甚至因为 CDN 节点部署在海外直接把全部流量拒之门外。正确的做法是先通过X-Forwarded-For或X-Real-IP还原真实客户端 IP再交给 GeoIP 判断。2.3 “禁止国外 IP”不等于“只允许中国 IP”这两个概念在逻辑上并不完全等价。某些业务可能还需要放行中国香港、中国澳门、中国台湾地区的用户或者放行特定的海外办公室出口 IP。因此面试中更稳妥的表达是优先说明需求边界再选择“白名单模式”或“黑名单模式”并说明两种模式的取舍。理解了这些背景下面进入方案对比。三、Nginx 禁止国外 IP 的六大方案总览在正式讲配置之前先用一张表把常见方案列清楚。这张表也是面试时可以直接用来展示思路框架的“答案地图”。方案核心原理优点缺点适用场景1. 静态 IP 白名单 allow/deny手工维护中国 IP 段逐段 allow最后 deny all依赖少逻辑直观维护成本高、匹配慢、易失效极小规模、IP 段固定的内网系统2. ngx_http_geoip_module基于 MaxMind GeoIP 旧版数据库按国家代码判断配置简单Nginx 原生支持MaxMind 已停止更新旧版数据库历史版本、学习原理3. ngx_http_geoip2_module基于 MaxMind GeoLite2 新版 mmdb 数据库数据库仍在更新精度高支持城市/ISP需编译第三方模块生产环境主流选择4. geo 自定义国家代码映射通过 HTTP 请求头或变量把国家代码映射为 0/1灵活可结合 CDN 传入的地理信息依赖上游提供准确的地理信息CDN/网关已带国家头时使用5. OpenResty / Lua 动态判断用 Lua 调用 mmdb 库或 Redis 缓存做判断能力强可做复杂策略和动态更新技术栈更重需熟悉 Lua复杂 ACL、动态黑名单场景6. 网络层辅助iptables/ipset在系统网络层直接丢弃国外 IP 流量性能高不经过应用判断维护复杂容易误伤流量极大且 Nginx 压力过大的场景下面逐个展开重点讲生产中最常用的 GeoIP2 方案。四、方案一静态 IP 白名单 allow/deny4.1 基本原理Nginx 的ngx_http_access_module提供了allow和deny指令可以基于来源 IP 或网段进行访问控制。它的判断逻辑是从上到下依次匹配命中第一条规则后立即停止。因此一种直观的做法是准备一份“中国大陆 IP 段列表”对每个 IP 段执行allow最后加一条deny all兜底。4.2 示例配置http { # 假设已经拿到中国主要 IP 段 include /etc/nginx/conf.d/china_ip_list.conf; server { listen 80; server_name example.com; location / { allow 1.0.1.0/24; allow 1.0.2.0/23; # ... 此处省略大量 IP 段 deny all; proxy_pass http://backend; } } }其中china_ip_list.conf的内容类似allow 1.0.1.0/24; allow 1.0.2.0/23; allow 1.0.8.0/21; allow 1.0.32.0/19; allow 1.1.0.0/24; # 更多网段...4.3 致命缺陷这个方案最大的问题不是“写不出来”而是“写出来没法长期用”规则数量巨大中国 IP 段约有几千条全部写进 Nginx 配置后启动解析和请求匹配都会变慢。IPv6 难以覆盖中国 IPv6 地址正在快速增长手工整理基本跟不上。更新困难需要定期从 APNIC 拉取最新分配数据再生成配置再 reload链路很长。误伤风险高CDN 回源 IP、内部服务 IP、监控探针 IP 如果没有列入白名单都会被一并拒绝。所以这个方案适合作为面试中的“基础分”答案提一嘴再顺势引出更优方案。它本身并不适合直接用在大型生产系统上。五、方案二使用 ngx_http_geoip_module 判断国家5.1 为什么要有 GeoIP 模块既然手工维护 IP 段太麻烦自然就会想到“有没有现成的数据库只要告诉我某个 IP 属于哪个国家就行”。MaxMind 公司提供的 GeoIP 数据库正是干这件事的。Nginx 官方在较早版本中内置了ngx_http_geoip_module它直接读取 MaxMind 的 GeoIP 旧版数据库.dat 文件把客户端 IP 映射为国家代码、地区代码、城市等变量。5.2 检查模块是否已编译nginx -V 21 | tr \n | grep geoip如果输出中包含--with-http_geoip_module说明支持旧版 GeoIP如果包含--with-http_geoip_moduledynamic说明是以动态模块方式加载。如果当前 Nginx 没有该模块需要重新编译。这里给出典型编译方式./configure --with-http_geoip_module make make install5.3 获取 GeoIP 数据库旧版 GeoIP 数据库包括国家库和城市库。MaxMind 已经停止更新旧版 GeoIP Legacy 数据库但历史文件仍可用于学习和测试。mkdir -p /etc/nginx/geoip cd /etc/nginx/geoip # 下载 GeoIP Legacy 国家库已停止维护仅演示 wget https://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz gunzip GeoIP.dat.gz5.4 配置示例http { geoip_country /etc/nginx/geoip/GeoIP.dat; geoip_city /etc/nginx/geoip/GeoLiteCity.dat; map $geoip_country_code $allow_visit { default 0; # 默认拒绝 CN 1; # 中国放行 HK 1; # 按业务需要决定是否放行 MO 1; TW 1; } server { listen 80; server_name example.com; if ($allow_visit 0) { return 403; } location / { proxy_pass http://backend; } } }5.5 该方案的问题MaxMind 已于 2019 年起停止更新 GeoIP Legacy 数据库数据会越来越旧。if指令在 Nginx 中一直有“ifis evil”的说法滥用会影响可读性某些上下文中还会产生意外行为。旧版模块不支持 IPv6 的国家判断GeoIP2 才支持。因此现代生产环境通常转向 GeoIP2 方案。六、方案三重点ngx_http_geoip2_module 实战详解这是本文的核心方案也是面试中最应该重点展开的部分。它使用 MaxMind 仍在持续更新的 GeoLite2/GeoIP2 mmdb 数据库支持 IPv4、IPv6、城市、经纬度、ISP、ASN 等多维信息。6.1 为什么选择 GeoIP2对比项GeoIP Legacy.datGeoIP2.mmdb数据库格式旧版二进制格式MaxMind DBmmdb格式是否继续更新已停止更新持续更新IPv6 支持弱支持信息维度国家、城市国家、城市、经纬度、ISP、ASN 等Nginx 原生模块ngx_http_geoip_module需第三方ngx_http_geoip2_module6.2 安装 libmaxminddb 依赖库GeoIP2 数据库需要libmaxminddb库来读取。以 CentOS / Ubuntu 为例# Ubuntu / Debian apt update apt install -y libmaxminddb0 libmaxminddb-dev mmdb-bin CentOS / RHEL yum install -y epel-release yum install -y libmaxminddb libmaxminddb-devel mmdb-bin安装完成后可以通过mmdblookup命令验证数据库读取是否正常。6.3 编译 ngx_http_geoip2_module该模块由 leev 维护需要配合 Nginx 源码一起编译。假设 Nginx 安装在/usr/local/nginx# 下载与当前 Nginx 相同版本的源码 cd /usr/local/src wget http://nginx.org/download/nginx-1.24.0.tar.gz tar -zxf nginx-1.24.0.tar.gz cd nginx-1.24.0 下载 geoip2 模块 git clone https://github.com/leev/ngx_http_geoip2_module.git 查看当前 Nginx 编译参数尽量保持一致并追加模块 nginx -V 重新编译 ./configure --with-compat --add-dynamic-module/usr/local/src/nginx-1.24.0/ngx_http_geoip2_module 其他原有参数... make modules cp objs/ngx_http_geoip2_module.so /usr/local/nginx/modules/编译完成后在 Nginx 主配置顶部加载动态模块load_module modules/ngx_http_geoip2_module.so;6.4 获取 GeoLite2 数据库MaxMind 要求免费用户先注册账号才能下载 GeoLite2 数据库。生产环境建议使用官方授权方式获取 GeoIP2 商业版数据库。这里演示 GeoLite2 的获取流程。前往 MaxMind 官网注册账号并登录在 Account 页面生成 License Key使用geoipupdate工具配置自动更新或手动下载数据库文件。使用geoipupdate的配置示例apt install -y geoipupdatecat /etc/GeoIP.conf # 如下内容 AccountID 你的账户ID LicenseKey 你的LicenseKey EditionIDs GeoLite2-Country GeoLite2-City DatabaseDirectory /usr/share/GeoIPgeoipupdate ls -l /usr/share/GeoIP/ # GeoLite2-Country.mmdb # GeoLite2-City.mmdb6.5 Nginx 配置实战下面是一份可直接落地的完整配置。它先用geoip2把国家代码提取成变量再用map把国家代码映射为“是否放行”的标志最后在location阶段判断并拦截。http { # 国家库提取国家代码与国家名称 geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip2_country_code source$remote_addr country iso_code; $geoip2_country_name source$remote_addr country names zh-CN; } # 可选城市库用于更精细控制 # geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb { # $geoip2_city_name source$remote_addr city names zh-CN; # } 国家代码到放行标志的映射 map $geoip2_country_code $allow_visit { default 0; # 默认拒绝 CN 1; # 中国大陆放行 HK 1; # 以下三个按业务边界决定 MO 1; TW 1; } server { listen 80; server_name example.com; location / { if ($allow_visit 0) { return 403; } proxy_pass http://backend; } } }这里有几点需要重点说明变量来源geoip2指令中的source$remote_addr表示用直连 Nginx 的对端 IP 来判断。如果前面还有 CDN 或反向代理应先还原真实客户端 IP再把变量名替换为$realip_remote_addr或自定义变量。用 map 而不是一串 if国家代码到 0/1 的转换集中在map中可读性好、维护成本低也便于后续增加或调整放行地区。if 只做简单判断这里是改写阶段的return 403属于相对安全的用法。复杂路由逻辑不要堆在if里。403 与 444 的取舍403会告诉对方“禁止访问”更友好444则是 Nginx 的“直接关闭连接”写法不给响应。对外暴露的站点一般用 403 更便于定位问题。6.6 验证与排错配置完成后按下面的顺序验证不要一上来就重载生产环境。# 1. 检查配置语法 nginx -t 2. 用 mmdblookup 验证某个 IP 的归属国 mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 country iso_code 3. 语法通过后再重载 nginx -s reload 4. 模拟测试 curl -I http://example.com # 国内 IP 应正常返回 海外 IP 应返回 403建议先只在日志里观察确认国家识别无误后再落地拦截。可以临时在日志格式中打印国家代码log_format geo $remote_addr - $geoip2_country_code - $request; access_log /var/log/nginx/access.log geo;观察一段时间确认没有误伤后再加return 403的拦截逻辑。若排查时发现国家代码为空优先检查 mmdb 文件路径、模块是否加载成功以及请求是否携带了真实客户端 IP。七、方案四geo 自定义国家代码映射这个方案的思路是“不自己查数据库直接用上游给的地理信息”。很多 CDN 会在回源请求时带上地理位置请求头例如 Cloudflare 的CF-IPCountry、阿里云 CDN 的地理头等。Nginx 只需要读取这些头并做映射。map $http_cf_ipcountry $allow_visit { default 0; # 默认拒绝 CN 1; HK 1; MO 1; TW 1; } server { listen 80; server_name example.com; location / { if ($allow_visit 0) { return 403; } proxy_pass http://backend; } }这个方案最大的优点是零数据库、零第三方模块、性能极好。但它有一个前提条件必须保证来源足够可信。如果源站直接暴露在公网攻击者可以自己伪造CF-IPCountry: CN来绕过限制。因此生产环境一定要配合以下两点通过set_real_ip_from白名单上游 CDN 网段并开启real_ip_recursive只信任经过认证的回源节点。在边缘层就只允许 CDN 回源源站安全组或防火墙放行 CDN 回源网段其他来源直接拒绝。八、方案五OpenResty / Lua 动态判断如果 Nginx 层需要更复杂的策略例如结合 Redis 缓存、动态黑白名单、按用户维度计数等可以升级到 OpenResty用 Lua 调用lua-resty-maxminddb或resty.geoip2实现。http { lua_shared_dict geo_cache 10m; server { listen 80; location / { access_by_lua_block { local geoip2 require resty.geoip2 local db, err geoip2.open(/usr/share/GeoIP/GeoLite2-Country.mmdb) if not db then ngx.log(ngx.ERR, fail to open geoip2: , err) return ngx.exit(500) end local res, gerr db:lookup(ngx.var.remote_addr) local country res and res.country and res.country.iso_code if country ~ CN then return ngx.exit(403) end } proxy_pass http://backend; } } }这个方案的能力最强适合复杂 ACL、灰度控制、动态更新等场景但技术栈更重要求团队熟悉 OpenResty 和 Lua并且要注意 mmdb 对象的复用和缓存避免每个请求都重复打开数据库。九、方案六网络层辅助 iptables/ipset当流量极大、Nginx 压力已经很高时可以在系统网络层提前拦掉一部分垃圾流量。ipset能把大量网段装进内核哈希表再由iptables快速匹配性能非常高。# 创建按网段匹配的集合 ipset create cn hash:net family inet maxelem 65536 从文件批量加载中国 IP 段 while read -r net; do ipset add cn $net 2/dev/null done china_ips.txt 对中国 IP 放行或直接处理 iptables -I INPUT -m set --match-set cn src -j RETURN要注意这种方案脱离了应用层语义容易出现“误伤 CDN 回源 IP、内网 IP、监控探针 IP”的问题而且iptables规则本身也需要和服务器防火墙整体策略一起维护。通常只在明确的防护目标下作为 Nginx 之外的第二层辅助不推荐把它当作唯一手段。十、生产环境避坑要点无论选择哪个方案上线前都要重点检查以下事项真实客户端 IP有 CDN、WAF、负载均衡时必须用real_ip模块还原真实 IP否则$remote_addr全是上游节点地址判断结果完全失真。数据库自动更新GeoLite2 会周期性更新建议用geoipupdate配合 cron 定时拉取并在更新后执行nginx -s reload。保留白名单入口健康检查、监控探针、内部服务、搜索引擎爬虫等需要单独放行不要被“默认拒绝”误伤。先观察再拦截先在 access_log 中打印国家代码运行一段时间观察命中情况确认无误后再开启return 403。性能和可读性优先用geoip2加map少写大量if保持配置可读、可测试。十一、面试高分回答框架面试时不要一上来就背配置建议按下面的结构回答既展示思路又体现工程化思维先确认需求边界是“只允许中国 IP”还是“禁止明显国外 IP”是否需要放行港澳台或特定海外出口先对齐架构询问是否存在 CDN、WAF、反向代理因为真实 IP 的获取方式会直接决定后续方案。给出方案对比静表白名单、GeoIP 旧模块、GeoIP2、上游地理位置头、Lua 动态判断、网络层辅助简述各自的取舍和适用场景。推荐并落地主方案生产用ngx_http_geoip2_module加map说明数据库更新、真实 IP 还原、白名单放行等关键细节。补充风险和优化提到灰度观察、监控告警、与其他防护层协同展示完整工程能力。这样回答下来即使不把所有配置写全也足以让面试官看到你对这个问题的整体把控。十二、总结“用 Nginx 禁止国外 IP”这道题表面考的是配置实际上考的是对访问控制、GeoIP 生态、真实 IP 获取、性能与维护成本的综合理解。总结如下静态 IP 白名单适合“基础分”回答和极小规模场景不适合大型生产。ngx_http_geoip_module是旧方案数据库已停止更新主要用于理解原理。ngx_http_geoip2_module是当前生产主流配合 GeoLite2 数据库和map使用最稳。CDN 上游地理头、OpenResty Lua、iptables/ipset 是不同场景下的有效补充。上线前必须解决真实 IP 还原、数据库更新、白名单放行和灰度观察四个问题。把这一套思路讲清楚并且能动手跑通 GeoIP2 方案这道面试题就从“送命题”变成了你的加分项。