Docker+Nginx环境下Certbot验证acme-challenge报404问题求助
我在Ubuntu 24.04 LTS(GNU/Linux 6.8.0-36-generic x86_64)服务器上用Docker部署Django站点,docker-compose up --build执行后构建无报错,但访问/.well-known/acme-challenge/和/.well-known/acme-challenge/test-file/时出现404错误,目标是通过Let's Encrypt和Certbot配置HTTPS并实现证书自动续期。
错误日志
nginx_1 | 123.45.67.89 - - [06/Aug/2024:11:06:34 +0000] "GET /.well-known/acme-challenge/ HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" "-" nginx_1 | 2024/08/06 10:20:00 [error] 29#29: *12 "/var/www/allhoa/certbot/.well-known/acme-challenge/test-file/index.html" is not found (20: Not a directory), client: 123.45.67.891, server: mydjangosite.com, request: "GET /.well-known/acme-challenge/test-file/ HTTP/1.1", host: "www.mydjangosite.com" nginx_1 | 123.45.67.891 - - [06/Aug/2024:10:20:00 +0000] "GET /.well-known/acme-challenge/test-file/ HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" "-" nginx_1 | 123.45.67.891 - - [06/Aug/2024:10:20:50 +0000] "GET / HTTP/1.1" 200 27800 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" "-" web_1 | 172.18.0.6 - - [06/Aug/2024:10:20:50 +0000] "GET / HTTP/1.0" 200 27800 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
相关配置文件
/var/www/mydjangosite/config/gunicorn.conf.py
import multiprocessing bind = "0.0.0.0:9000" workers = multiprocessing.cpu_count() * 2 + 1 loglevel = 'info' errorlog = '-' accesslog = '-'
/var/www/mydjangosite/config/nginx.conf
user nginx; worker_processes auto; pid /var/run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server { listen 80; # Listen on IPv4 listen [::]:80; # Listen on IPv6 server_name mydjangosite.com www.mydjangosite.com; location /.well-known/acme-challenge/ { root /var/www/mydjangosite/config/www; } location / { proxy_pass http://web:9000; 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 /static/ { alias /code/staticfiles/; } location /media/ { alias /code/media/; } } }
/var/www/mydjangosite/docker-compose.yml
# version: '3.8' # not required volumes: postgres_data: {} static_volume: {} # certbot-etc: {} # certbot-var: {} services: nginx: image: nginx:latest env_file: - .env volumes: - ./config/nginx.conf:/etc/nginx/nginx.conf - ./config/certbot/conf/options-ssl-nginx.conf:/etc/letsencrypt/options-ssl-nginx.conf - ./config/certbot/conf/ssl-dhparams.pem:/etc/letsencrypt/ssl-dhparams.pem # - .:/code - static_volume:/code/staticfiles - ./config/certbot/conf:/etc/letsencrypt - ./config/certbot/www:/var/www/mydjangosite/config/certbot/www ports: - "80:80" - "443:443" depends_on: - web command: nginx -g 'daemon off;' certbot: image: certbot/certbot volumes: - ./config/certbot/conf:/etc/letsencrypt - ./config/certbot/www:/var/www/allhoa/config/certbot/www - ./certbot-entrypoint.sh:/certbot-entrypoint.sh entrypoint: ["/certbot-entrypoint.sh"] web: build: context: . command: > bash -c " cat .env && pip install --default-timeout=100 --retries=5 -r requirements.txt && python manage.py migrate && python manage.py collectstatic --no-input && gunicorn --config ./config/gunicorn.conf.py allhoa.wsgi:application" environment: - DATABASE_URL=postgres://${DBUSER}:${DBPASSWORD}@${DBHOST}:${DBPORT}/${DBNAME} env_file: - .env volumes: - .:/code - static_volume:/code/staticfiles ports: - "9000:9000"
/var/www/mydjangosite/Dockerfile
# Use an official Python runtime as a parent image FROM python:latest # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 # Set the working directory in the container WORKDIR /code # Copy the project code COPY . /code/ # Install any needed packages specified in requirements.txt RUN apt-get update && apt-get install -y libxslt-dev libxml2-dev libpam-dev libedit-dev && apt-get clean && rm -rf /var/lib/apt/lists/* # RUN pip install --upgrade pip && pip install -r requirements.txt RUN pip install --upgrade pip && pip install --default-timeout=100 --retries=5 -r requirements.txt RUN python manage.py collectstatic --no-input # Collect static files RUN python manage.py collectstatic --no-input # Command to run the Django application CMD ["gunicorn", "mydjangosite.wsgi:application", "--bind", "0.0.0.0:9000"]
问题排查与修复
从错误日志和配置文件来看,核心问题是路径映射不匹配,导致Nginx找不到ACME挑战文件,以下是具体修复步骤:
1. 修正Nginx的ACME路径配置
Nginx配置中location /.well-known/acme-challenge/的root路径与docker-compose挂载路径不匹配,修改nginx.conf中的对应配置:
location /.well-known/acme-challenge/ { root /var/www/mydjangosite/config/certbot/www; }
2. 修正Certbot的挂载路径拼写错误
Certbot容器挂载路径中的allhoa是拼写错误,需与Nginx容器内路径保持一致,修改docker-compose.yml中certbot服务的volumes:
certbot: image: certbot/certbot volumes: - ./config/certbot/conf:/etc/letsencrypt - ./config/certbot/www:/var/www/mydjangosite/config/certbot/www - ./certbot-entrypoint.sh:/certbot-entrypoint.sh entrypoint: ["/certbot-entrypoint.sh"]
3. 添加HTTPS监听配置
当前nginx.conf仅配置了80端口,需添加443端口的HTTPS配置块:
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name mydjangosite.com www.mydjangosite.com; ssl_certificate /etc/letsencrypt/live/mydjangosite.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mydjangosite.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location / { proxy_pass http://web:9000; 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 /static/ { alias /code/staticfiles/; } location /media/ { alias /code/media/; } } # 证书颁发成功后,可添加HTTP强制跳转HTTPS配置 server { listen 80; listen [::]:80; server_name mydjangosite.com www.mydjangosite.com; # 保留ACME挑战路径,其余请求跳转HTTPS location /.well-known/acme-challenge/ { root /var/www/mydjangosite/config/certbot/www; } location / { return 301 https://$host$request_uri; } }
4. 配置Certbot自动续期
确保certbot-entrypoint.sh包含续期逻辑,示例脚本如下:
#!/bin/sh set -e # 获取/续期证书 certbot certonly --webroot -w /var/www/mydjangosite/config/certbot/www -d mydjangosite.com -d www.mydjangosite.com --email your-email@example.com --agree-tos --non-interactive # 续期后重启Nginx生效 docker kill -s HUP nginx_nginx_1
给脚本添加执行权限:chmod +x certbot-entrypoint.sh
5. 测试ACME路径有效性
修复配置后重启服务:docker-compose down && docker-compose up --build,创建测试文件验证路径:
echo "test-content" > ./config/certbot/www/.well-known/acme-challenge/test-file
访问http://www.mydjangosite.com/.well-known/acme-challenge/test-file,若能看到"test-content"则配置正确。
内容的提问来源于stack exchange,提问作者Alex Winkler

