Nginx配置变量无法解析求值问题求助
我最近在给Nginx 1.27配置多个子域名,这些子域名的配置大部分都重复,所以想用变量来简化配置,但发现变量根本没被解析出来,头疼得很!
我的配置是这样的:foo.bar.com.conf 文件内容:
server { set $subdomain foo; include common_config; }
common_config 文件内容:
listen 80; server_name $subdomain.bar.com; root /www/bar.com/public_html; access_log /www/bar.com/logs/$subdomain.access.log; error_log /www/bar.com/logs/$subdomain.error.log;
运行 nginx -t 显示配置没问题,但实际启动后,日志文件居然是 $subdomain.error.log 而不是预期的 foo.error.log!我试过加引号、用 ${subdomain} 的写法,结果生成的文件名变成了 ${subdomain}.error.log,完全没效果。
问题原因分析
其实这是Nginx的配置阶段特性导致的:
set指令定义的变量属于请求阶段变量,只有当实际请求进来的时候才会被赋值;- 而
access_log、error_log这类涉及文件路径的指令,是在Nginx启动加载配置的阶段就会被解析并创建文件(如果不存在的话),这时候$subdomain变量还没有被赋值,所以Nginx就直接把变量名当成了文件名的一部分。 - 另外,
server_name这里其实也有问题,虽然语法检查通过,但server_name不支持使用变量(除非你用通配符或者正则,但变量在这里不会被解析),所以这个配置实际上也达不到你预期的匹配子域名的效果。
解决方案
针对你的场景,推荐两种更合适的方式:
方案1:使用Nginx的map指令(推荐)
map 指令是在配置加载阶段就会被处理的,可以用来根据请求的主机名提取子域名,这样日志路径就能正确解析了:
首先在http块中添加map配置:
http { map $host $subdomain { ~^(?<sub>.+)\.bar\.com$ $sub; default ""; } # 其他http级别的配置... }
然后修改common_config文件:
listen 80; server_name *.bar.com; # 用通配符匹配所有bar.com的子域名 root /www/bar.com/public_html; access_log /www/bar.com/logs/$subdomain.access.log; error_log /www/bar.com/logs/$subdomain.error.log;
这样不管是foo.bar.com还是其他子域名,Nginx都会自动提取子域名部分,日志文件也会正确命名。
方案2:使用include结合配置片段(适合子域名数量固定的情况)
如果你的子域名数量不多且固定,可以为每个子域名创建单独的配置片段,然后用include引入公共部分,同时在每个server块中直接硬编码子域名:
比如foo.bar.com.conf:
server { listen 80; server_name foo.bar.com; root /www/bar.com/public_html; access_log /www/bar.com/logs/foo.access.log; error_log /www/bar.com/logs/foo.error.log; # 其他公共配置可以单独写一个文件include进来 include common_other_config; }
这种方式虽然没有变量那么灵活,但胜在简单直接,不会有变量解析的问题。
额外提示
如果一定要使用变量来指定日志路径,还有一个办法是开启open_log_file_cache,不过这个是用来缓存日志文件句柄的,而且需要确保变量在请求阶段能被正确解析,但这种方式不如map方案可靠,不推荐作为主要解决方案。
备注:内容来源于stack exchange,提问作者jhilgeman

