Obrigado Justin e Michael por me apontar na direção certa, é de fato um bloco de localização causando meus problemas, em particular:
location / {
try_files $uri $uri/ @server;
error_page 403 = @server;
}
Basicamente, eu estava tentando ser inteligente e pegar o erro $uri/ (403 acontece quando você tenta acessar uma pasta que existe, mas não tem nenhum arquivo de índice ou auto-indexação) e também redireciona para @server block.
Mas não é try_files $uri $uri/ @server; sozinho o suficiente?
Imagine que você está tentando acessar http://example.com/ , nginx dirá, oh esta pasta existe (é seu root ) mas nenhum índice encontrado, e lança 403 em vez de passar para @server bloco.
Assim, minha solução 403, mas não percebi que ela vem com um custo: isso significa que nginx já pegou um erro e usou error_page no mesmo bloco location para manipulá-lo (passando-o para @server ).
Junte isso com meus testes na pergunta, isso sugere que nginx (v1.7.x) irá ignorar mais a diretiva error_page neste ponto e usar o padrão 502 ao invés.
A parte interessante: como podemos resolver isso?
Minha solução é configurar uma rota raiz exata em vez disso, agora capturar 403 na raiz não é mais necessário, e error_page funciona como pretendido.
error_page 502 /error-502.html;
location = /error-502.html {
internal;
root /srv/example.com/html;
}
location = / {
try_files $uri @server;
}
location / {
try_files $uri $uri/ @server;
}