直接用 $remote_addr 做 IP 分发会出错,因为经 CDN 或反向代理后 $remote_addr 变为代理 IP;需先通过 set_real_ip_from 和 real_ip_header 获取真实客户端 IP,再结合 geoip2 模块查国家码,最后用 map 映射至对应 upstream 实现精准地域路由。

为什么直接用 Nginx 的 $remote_addr 做 IP 分发会出错
因为 Composer 请求走的是 HTTP,但默认情况下,如果上游有 CDN、负载均衡或反向代理(比如阿里云 SLB、Cloudflare),$remote_addr 拿到的其实是代理 IP,不是真实客户端 IP。结果就是所有用户被分到同一个镜像节点,智能路由失效。
必须先确保 Nginx 能拿到真实 IP,再做地理分发:
- 确认上游已设置
X-Forwarded-For或X-Real-IP头(常见于云厂商控制台或 LB 配置) - 在 Nginx 配置中启用
set_real_ip_from,并指定可信代理网段(如10.0.0.0/8、172.16.0.0/12、192.168.0.0/16) - 用
real_ip_header X-Forwarded-For或X-Real-IP(按上游实际发送的头名选) - 之后才能安全使用
$remote_addr—— 此时它已是真实客户端 IP
如何用 GeoIP2 + Nginx 实现按国家/地区路由
单纯靠 IP 归属库查城市级位置开销大、精度低,且 Composer 请求无 Cookie、无 UA 差异,最适合按国家(country code)做粗粒度分发。推荐用 MaxMind GeoIP2 + ngx_http_geoip2_module:
- 下载 GeoLite2 Country 数据库(
GeoLite2-Country.mmdb),放到 Nginx 可读路径(如/usr/share/GeoIP/GeoLite2-Country.mmdb) - 编译 Nginx 时需加入
--add-dynamic-module=../ngx_http_geoip2_module,或用预编译支持模块的发行版(如 OpenResty) - 在
http块中加载数据库:geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip2_data_country_code country iso_code; } - 用
map把国家码映射到镜像域名:map $geoip2_data_country_code $mirror_host { default packagist.org; CN mirror.sjtu.edu.cn; US us.phpcomposer.com; JP jp.phpcomposer.com; }
如何把 Composer 请求精准代理到对应镜像,且不破坏语义
Composer 的包请求是标准 HTTP GET,路径形如 /packages.json、/dist/vendor/pkg.zip,不能简单 rewrite,否则会丢失原始 Host 和路径语义,导致签名验证失败或 404。
正确做法是用 proxy_pass + 动态 upstream:
- 定义多个 upstream(每个镜像一个),例如:
upstream mirror_cn { server mirror.sjtu.edu.cn; } - 在 location 中根据
$mirror_host动态选择 upstream:proxy_pass https://$mirror_host;(注意:必须用 HTTPS,且 Nginx 要开启proxy_ssl_server_name on;) - 透传关键头:
proxy_set_header Host $mirror_host;、proxy_set_header X-Real-IP $remote_addr;、proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - 禁用缓存(Composer 包元数据常更新):
proxy_cache off;
容易忽略的 TLS 和证书问题
所有镜像站都走 HTTPS,Nginx 作为中间代理必须能校验下游证书,否则 proxy_pass 会报 SSL certificate error 或直接 502。
常见踩坑点:
-
proxy_ssl_trusted_certificate必须指向系统 CA 包(如/etc/ssl/certs/ca-certificates.crt),不能留空或指向错误路径 - 若镜像站用了 Let’s Encrypt 泛域名证书,而你的 Nginx 版本
- 某些高校镜像(如 SJTU)启用了 HSTS,若代理层未透传
Strict-Transport-Security头,Composer 客户端可能拒绝降级,但更大概率是直接失败而非降级 - 测试时用
curl -v https://your-proxy-domain/packagist.org/packages.json看是否返回 200 且 Content-Type 是 application/json,而不是 Nginx 默认页或证书错误页
IP 地理路由本身不复杂,难在链路里每一段都要对齐:真实 IP 传递、GeoIP2 加载、动态 proxy_pass、TLS 透传、Header 保真 —— 少一环,Composer 就报 Could not fetch 或 Invalid response。











