Docker环境下Nginx反向代理子目录至Apache容器失效求助
我来帮你排查这个反向代理的问题!既然你已经确认所有Apache容器本身能正常运行,那问题大概率出在Nginx的location配置细节,或者Apache应用对代理路径的适配处理上,下面给你一步步拆解解决方案:
1. 先搞定Nginx的核心配置坑:proxy_pass的斜杠问题
这是子目录反向代理最容易踩的雷!proxy_pass末尾的斜杠决定了Nginx如何传递URI给后端Apache,直接影响路径匹配:
错误示例(导致404的常见原因)
如果你的配置是这样:
location /parts { proxy_pass http://apache-parts; }
当用户请求/parts/foo时,Nginx会把完整的/parts/foo传给Apache,而你的Apache应用是部署在根路径(/)下的,自然找不到/parts/foo这个资源。
正确配置二选一
方式一:带尾斜杠的location + proxy_pass
location /parts/ { proxy_pass http://apache-parts/; # 必须添加的代理头,保证后端能正确识别请求来源 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
这里location /parts/和proxy_pass http://apache-parts/都带尾斜杠,Nginx会自动把/parts/后面的URI部分传递给Apache,比如/parts/foo会被代理到http://apache-parts/foo,完美匹配后端应用的根路径。
方式二:用rewrite手动调整URI
如果不想用带尾斜杠的location,可以通过rewrite剥离前缀:
location /parts { rewrite ^/parts(.*)$ $1 break; proxy_pass http://apache-parts; # 同样添加代理头 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
2. 解决应用资源路径的适配问题
很多应用是基于根路径开发的,比如CSS/JS用绝对路径引用(/css/style.css),当代理到/parts下时,浏览器会直接请求/css/style.css(走到Nginx的根location),而不是/parts/css/style.css,导致页面样式/脚本加载失败。
解决办法两种:
方法一:修改应用代码(推荐)
- 如果是静态站点:把所有绝对路径改成相对路径,或者在HTML头部添加
<base href="/parts/">,让浏览器自动给资源路径加上前缀。 - 如果是动态应用(比如PHP、Python框架):配置应用的base URL为
/parts,让框架生成的所有链接自动带上前缀。
方法二:用Nginx的sub_filter替换响应内容
如果没法修改应用代码,可以让Nginx自动替换响应里的根路径前缀:
location /parts/ { proxy_pass http://apache-parts/; proxy_set_header Host $host; # 替换响应中的绝对路径为/parts开头 sub_filter 'href="/' 'href="/parts/'; sub_filter 'src="/' 'src="/parts/'; sub_filter_once off; # 替换所有匹配项,不是只替换第一个 }
注意:这个功能需要Nginx编译了ngx_http_sub_module模块,官方的Nginx镜像默认已经包含。
3. 完整配置示例(Docker Compose + Nginx)
docker-compose.yml
version: '3.8' services: nginx-proxy: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - apache-root - apache-parts - apache-wiki apache-root: image: httpd:alpine volumes: - ./root-app:/usr/local/apache2/htdocs/ apache-parts: image: httpd:alpine volumes: - ./parts-app:/usr/local/apache2/htdocs/ apache-wiki: image: httpd:alpine volumes: - ./wiki-app:/usr/local/apache2/htdocs/
nginx.conf
events {} http { include /etc/nginx/mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; # 代理根路径到apache-root location / { proxy_pass http://apache-root/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 代理/parts到apache-parts location /parts/ { proxy_pass http://apache-parts/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 按需启用sub_filter # sub_filter 'href="/' 'href="/parts/'; # sub_filter 'src="/' 'src="/parts/'; # sub_filter_once off; } # 代理/wiki到apache-wiki location /wiki/ { proxy_pass http://apache-wiki/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 按需启用sub_filter # sub_filter 'href="/' 'href="/wiki/'; # sub_filter 'src="/' 'src="/wiki/'; # sub_filter_once off; } } }
4. 测试与排查
- 用
curl测试代理是否生效:curl http://localhost/parts/,看返回内容是否和直接访问Apache容器一致。 - 如果还是有问题,查看Nginx日志:
docker logs nginx-proxy,根据日志里的错误信息(比如404、502)进一步排查。
内容的提问来源于stack exchange,提问作者thenetimp

