基于nginx-proxy+acme-companion的多容器服务503错误排查求助
问题
拥有一台绑定A记录的VPS(域名mydomain.com,IPv4地址为IPADRESS),希望通过HTTPS分别访问部署在其上的Nextcloud实例(cloud.mydomain.com)和Django博客(blog.mydomain.com)。采用nginx-proxy+acme-companion的Docker方案,已完成以下操作:
创建docker网络:
sudo docker network create nginx-proxy启动反向代理容器,工作目录为
/home/ubuntu/,目录结构、nginx_setup/docker-compose.yml配置及nginx.tmpl模板如下:
目录结构:.├── mywebsite-django │ └── mysite │ ├── Dockerfile │ ├── blog │ ├── config │ ├── db.sqlite3 │ ├── docker-compose.yml │ ├── manage.py │ ├── mywebsite │ ├── nginx │ ├── requirements.txt │ └── staticfiles ├── nextcloud_setup │ ├── app │ │ ├── config │ │ ├── custom_apps │ │ ├── data │ │ └── themes │ ├── docker-compose.yml │ └── proxy │ ├── certs │ ├── conf.d │ ├── html │ └── vhost.d └── nginx_setup ├── certs │ ├── mydomain.com │ ├── blog.mydomain.com │ ├── default.crt │ ├── default.key │ └── dhparam.pem ├── conf.d │ └── default.conf ├── docker-compose.yml ├── html ├── nginx.tmpl ├── templates │ └── nginx.tmpl └── vhost.d └── defaultnginx_setup/docker-compose.yml配置:version: '3' services: nginx: image: nginx labels: com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true" container_name: nginx restart: unless-stopped logging: options: max-size: "10m" max-file: "3" ports: - "80:80" - "443:443" volumes: - /home/ubuntu/nginx_setup/conf.d:/etc/nginx/conf.d - /home/ubuntu/nginx_setup/vhost.d:/etc/nginx/vhost.d - /home/ubuntu/nginx_setup/html:/usr/share/nginx/html - /home/ubuntu/nginx_setup/certs:/etc/nginx/certs:ro environment: DEFAULT_HOST: "mydomain.com" nginx-gen: image: jwilder/docker-gen container_name: nginx-gen restart: unless-stopped volumes: - /home/ubuntu/nginx_setup/conf.d:/etc/nginx/conf.d - /home/ubuntu/nginx_setup/vhost.d:/etc/nginx/vhost.d - /home/ubuntu/nginx_setup/html:/usr/share/nginx/html - /home/ubuntu/nginx_setup/certs:/etc/nginx/certs:ro - /var/run/docker.sock:/tmp/docker.sock:rw - /home/ubuntu/nginx_setup/templates/:/etc/docker-gen/templates:ro command: -notify-sighup nginx -watch -only-exposed /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf nginx-letsencrypt: image: jrcs/letsencrypt-nginx-proxy-companion container_name: nginx-letsencrypt restart: unless-stopped volumes: - /home/ubuntu/nginx_setup/conf.d:/etc/nginx/conf.d - /home/ubuntu/nginx_setup/vhost.d:/etc/nginx/vhost.d - /home/ubuntu/nginx_setup/html:/usr/share/nginx/html - /home/ubuntu/nginx_setup/certs:/etc/nginx/certs:rw - /var/run/docker.sock:/var/run/docker.sock:rw environment: NGINX_DOCKER_GEN_CONTAINER: "nginx-gen" NGINX_PROXY_CONTAINER: "nginx" networks: default: external: name: nginx-proxynginx.tmpl模板:server { listen 80 default_server; server_name _; # This is just an invalid value which will never trigger on a real hostname. error_log /proc/self/fd/2; access_log /proc/self/fd/1; return 503; } {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} upstream {{ $host }} { {{ range $index, $value := $containers }} {{ $addrLen := len $value.Addresses }} {{ $network := index $value.Networks 0 }} {{/* If only 1 port exposed, use that */}} {{ if eq $addrLen 1 }} {{ with $address := index $value.Addresses 0 }} # {{$value.Name}} server {{ $network.IP }}:{{ $address.Port }}; {{ end }} {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var */}} {{ else if $value.Env.VIRTUAL_PORT }} {{ range $i, $address := $value.Addresses }} {{ if eq $address.Port $value.Env.VIRTUAL_PORT }} # {{$value.Name}} server {{ $network.IP }}:{{ $address.Port }}; {{ end }} {{ end }} {{/* Else default to standard web port 80 */}} {{ else }} {{ range $i, $address := $value.Addresses }} {{ if eq $address.Port "80" }} # {{$value.Name}} server {{ $network.IP }}:{{ $address.Port }}; {{ end }} {{ end }} {{ end }} {{ end }} } server { gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; server_name {{ $host }}; proxy_buffering off; error_log /proc/self/fd/2; access_log /proc/self/fd/1; location / { proxy_pass http://{{ trim $host }}; proxy_set_header Host $http_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; # HTTP 1.1 support proxy_http_version 1.1; proxy_set_header Connection ""; } }{{ end }}启动后日志无异常。
启动Django容器,
mywebsite-django/mysite/docker-compose.yml配置如下:version: '3' services: gunicorn: container_name: myblog build: . command: sh -c "python manage.py makemigrations && python manage.py migrate && python manage.py collectstatic --noinput && gunicorn --bind 0.0.0.0:8000 --workers 2 mywebsite.wsgi:application" volumes: - ./staticfiles:/static environment: VIRTUAL_HOST: blog.mydomain.com VIRTUAL_PORT: 8000 LETSENCRYPT_HOST: mydomain.com LETSENCRYPT_EMAIL: mymail@forletsecrypt.com ports: - "8000:8000" networks: default: external: name: nginx-proxy启动后日志无异常。
执行curl blog.mydomain.com返回503错误页面:
<html><head><title>503 Service Temporarily Unavailable</title></head><body><center><h1>503 Service Temporarily Unavailable</h1></center><hr><center>nginx/1.23.2</center></body></html>
附加信息:
docker网络列表:
NETWORK ID NAME DRIVER SCOPE ce90ed81eade bridge bridge local c6325fd6c267 host host local 834d9a715380 nginx-proxy bridge local 78c28ce57f15 none null local
ufw状态:
Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), deny (routed) New profiles: skip To Action From -- ------ ---- 80,443/tcp (Nginx Full) ALLOW IN Anywhere 22/tcp ALLOW IN Anywhere 80,443/tcp (Nginx Full (v6)) ALLOW IN Anywhere (v6) 22/tcp (v6) ALLOW IN Anywhere (v6)
排查与解决方案
1. 检查nginx-gen的配置生成情况
查看/home/ubuntu/nginx_setup/conf.d/default.conf文件,确认是否存在针对blog.mydomain.com的server块和upstream配置。如果没有,说明nginx-gen未识别到Django容器的VIRTUAL_HOST环境变量:
- 执行命令重启nginx-gen容器,触发配置重新生成:
重启后再次检查docker restart nginx-gendefault.conf是否生成对应配置。
2. 修正Django容器的LETSENCRYPT_HOST配置
当前Django容器的LETSENCRYPT_HOST设置为mydomain.com,但实际需要证书的域名是blog.mydomain.com,这会导致acme-companion无法正确生成证书,同时干扰nginx-proxy的路由逻辑:
- 修改Django的
docker-compose.yml,将LETSENCRYPT_HOST改为blog.mydomain.com:environment: VIRTUAL_HOST: blog.mydomain.com VIRTUAL_PORT: 8000 LETSENCRYPT_HOST: blog.mydomain.com LETSENCRYPT_EMAIL: mymail@forletsecrypt.com - 重启Django容器:
cd /home/ubuntu/mywebsite-django/mysite && docker-compose down && docker-compose up -d
3. 验证跨容器网络可达性
进入nginx容器,测试是否能访问Django容器的服务:
- 进入nginx容器:
docker exec -it nginx bash - 安装ping工具(若未安装):
apt update && apt install iputils-ping -y - ping Django容器名称
myblog,检查网络连通性:ping myblog - 直接访问Gunicorn服务端口:
curl http://myblog:8000
如果无法连通,重启相关容器确保它们正确加入nginx-proxy网络:
docker restart myblog nginx
4. 修正nginx.tmpl的网络IP获取逻辑
当前模板使用{{ index $value.Networks 0 }}获取容器网络信息,若容器加入多个网络,第一个可能不是nginx-proxy,导致IP获取错误:
- 修改
nginx.tmpl中的网络获取代码,指定从nginx-proxy网络获取IP:
将:
替换为:{{ $network := index $value.Networks 0 }}{{ $network := index $value.Networks "nginx-proxy" }} - 重启nginx-gen容器生效:
docker restart nginx-gen
5. 检查证书文件权限
若证书目录或文件权限不正确,nginx无法读取证书会导致路由失败:
- 修改certs目录及文件权限:
sudo chmod -R 644 /home/ubuntu/nginx_setup/certs sudo chown -R root:root /home/ubuntu/nginx_setup/certs - 重启nginx容器:
docker restart nginx
内容的提问来源于stack exchange,提问作者ar_ma_cs

