You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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方案,已完成以下操作:

  1. 创建docker网络:

    sudo docker network create nginx-proxy
    
  2. 启动反向代理容器,工作目录为/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
            └── default
    

    nginx_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-proxy
    

    nginx.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 }}
    

    启动后日志无异常。

  3. 启动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-gen
    
    重启后再次检查default.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:46:15