skip to Main Content

I’m trying to set up a Django project with docker + nginx following the tutorial Nginx and Let’s Encrypt with Docker in Less Than 5 Minutes.

The issue is when I run the script init-letsencrypt.sh I end up with failed challenges.

Here is the content of my script:

#!/bin/bash

if ! [ -x "$(command -v docker-compose)" ]; then
  echo 'Error: docker-compose is not installed.' >&2
  exit 1
fi

domains=(xxxx.yyyy.net www.xxxx.yyyy.net)
rsa_key_size=4096
data_path="./data/certbot"
email="[email protected]" # Adding a valid address is strongly recommended
staging=1 # Set to 1 if you're testing your setup to avoid hitting request limits

if [ -d "$data_path" ]; then
  read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
  if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
    exit
  fi
fi


if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
  echo "### Downloading recommended TLS parameters ..."
  mkdir -p "$data_path/conf/"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
  echo
fi

echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose -f docker-compose-deploy.yml run --rm --entrypoint "
  openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1
    -keyout '$path/privkey.pem' 
    -out '$path/fullchain.pem' 
    -subj '/CN=localhost'" certbot
echo


echo "### Starting nginx ..."
docker-compose -f docker-compose-deploy.yml up --force-recreate -d proxy
echo

echo "### Deleting dummy certificate for $domains ..."
docker-compose -f docker-compose-deploy.yml  run --rm --entrypoint "
  rm -Rf /etc/letsencrypt/live/$domains && 
  rm -Rf /etc/letsencrypt/archive/$domains && 
  rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
  domain_args="$domain_args -d $domain"
done

# Select appropriate email arg
case "$email" in
  "") email_arg="--register-unsafely-without-email" ;;
  *) email_arg="--email $email" ;;
esac

# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi

docker-compose  -f docker-compose-deploy.yml run --rm --entrypoint "
  certbot -v certonly --webroot -w /var/www/certbot 
    $staging_arg 
    $email_arg 
    $domain_args 
    --rsa-key-size $rsa_key_size 
    --agree-tos 
    --force-renewal" certbot
echo

echo "### Reloading nginx ..."
docker-compose  -f docker-compose-deploy.yml exec proxy nginx -s reload

And my nginx configuration file:

server {
    listen 80;
    server_name xxxx.yyyy.net;
       
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    
    location / {
        return 301 https://$server_name$request_uri;
    }

}

server {
    listen 443 ssl;
    server_name xxxx.yyyy.net;
    
    ssl_certificate /etc/letsencrypt/live/xxxx.yyyy.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/xxxx.yyyy.net/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 
    
    location /static {
        alias /vol/static;
    }

    location / {
        uwsgi_pass web:8000;
        include /etc/nginx/uwsgi_params;
    }

}

The output of the part that fails:

Requesting a certificate for xxxx.yyyy.net and www.xxxx.yyyy.net
Performing the following challenges:
http-01 challenge for xxxx.yyyy.net
http-01 challenge for www.xxxx.yyyy.net
Using the webroot path /var/www/certbot for all unmatched domains.
Waiting for verification...
Challenge failed for domain xxxx.yyyy.net
Challenge failed for domain www.xxxx.yyyy.net
http-01 challenge for xxxx.yyyy.net
http-01 challenge for www.xxxx.yyyy.net

Certbot failed to authenticate some domains (authenticator: webroot). The Certificate Authority reported these problems:
  Domain: xxxx.yyyy.net
  Type:   connection
  Detail: Fetching http://xxxx.yyyy.net/.well-known/acme-challenge/XJw9w39lRSSbPf-4tb45RLtTnSbjlUEi1f0Cqwsmt-8: Connection refused

  Domain: www.xxxx.yyyy.net
  Type:   connection
  Detail: Fetching http://www.xxxx.yyyy.net/.well-known/acme-challenge/b47s4WJARyOTS63oFkaji2nP7oOhiLx5hHp4kO9dCGI: Connection refused

Hint: The Certificate Authority failed to download the temporary challenge files created by Certbot. Ensure that the listed domains serve their content from the provided --webroot-path/-w and that files created there can be downloaded from the internet.

Cleaning up challenges
Some challenges have failed.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
ERROR: 1

One of the comments said:

comment

But there’s no further explanation as to how to solve it.

Check the certbot commit

4

Answers


  1. Chosen as BEST ANSWER

    Problem is nginx configuration file. The container fails to start up correctly because of missing certification files. I commented out the ssl server portion, rebuilt the image and executed the script again. Everything worked out just fine. After certificates were generated I just uncommented the ssl configuration, rebuilt the image and composed up the services.


  2. Had the same issue;
    The solution was ensuring I defined the volume blocks in both the nginx and certbot services correctly.

    //other services
    
    nginx:
    container_name: nginx
    image: nginx:1.13
    ports:
      - "80:80"
      - "443:443"
    
    volumes:
      - ./config/nginx/conf.d:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    
    certbot:
    container_name: certbot
    image: certbot/certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    
    Login or Signup to reply.
  3. Also if you are using EC2 as your cloud server don’t forget to add inbound rules for ports 80 and 443.

    Login or Signup to reply.
  4. A More Beginner-friendly Version!

    I can confirm that the first answer that was posted (remove all lines regarding SSL certificate registration/HTTPS redirection when first running the init-letsencrypt.sh) works perfectly!

    The lack of documentation is really annoying on this one, and i had to find the answer deep in the community section. Even for someone whose first language isn’t English this answer would be really difficult to find. I wish they documented more on this matter. 🙁

    So here are some of the steps that you have to follow to resolve this issue…

    1. Basically gotta remove all the HTTPS SSL-related stuff from both the docker-compose.yml and the nginx.conf / nginx/app.conf file.
    2. Then run the init-letsencrypt.sh script.
    3. Then add the HTTPS SSL-related stuff back to both the docker-compose.yml and the nginx.conf / nginx/app.conf file. (If you’re on Git, just revert your commits)
    4. Then run docker-compose up -d --build. Then run the init-letsencrypt.sh script again.

    Hope this helps, and wish y’all the best of luck!!

    P/S: The back-end stack I used was Flask + Celery (Allows Flask to Run Heavy Tasks Asyncronously) + Redis (A Bridge/Middleman Between Flask and Celery) + NGINX + Certbot all running inside individual docker containers, chained using docker-compose. I deployed it on a DigitalOcean Droplet VPS. (VPS is essentially a computer OS that runs on the internet, 24/7)

    For newbies, Docker: Think of Python’s virtualenv or Node.js’s localized node_modules but for OS-level/C-based dependencies. Like those that can be only installed through package managers such as Linux’s apt-get install, macOS’s brew install, or Windows’s choco install.

    Docker Compose: e.g. The client and the server may have different OS-level dependencies and you want to separate them so they don’t conflict with each other. You can only allow certain communications between by "chaining" them through docker-compose.

    What’s NGINX? It’s a reverse-proxy solution; TLDR: you can connect the domain/URL you purchased and direct it to your web app. Let’s Encrypt allows the server to have that green chain lock thing next to your address for secure communication.

    Also important thing to note: Do NOT install NGINX or Redis OUTSIDE of the Docker container on the Linux terminal! That will cause conflicts (ports 443 and 80 already being occupied). 443 is for HTTPS, 80 is for HTTP.

    These are the tutorial I used for setting up my tech stack:

    1. https://testdriven.io/blog/dockerizing-flask-with-postgres-gunicorn-and-nginx/
    2. https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71

    I can also share my docker-compose.yml file below for your reference:

    version: '3.8'
    services:
      web:
        build: .
        image: web
        container_name: web
        command: gunicorn --worker-class=gevent --worker-connections=1000 --workers=5 api:app --bind 0.0.0.0:5000
        volumes:
          - .:/usr/src/app
        environment:
          - CELERY_BROKER_URL=redis://redis:6379/0
          - CELERY_RESULT_BACKEND=redis://redis:6379/0
        depends_on:
          - redis
        expose:
          - 5000
    
      worker:
        build: .
        command: celery --app tasks.celery worker --loglevel=info
        volumes:
          - .:/usr/src/app
        environment:
          - CELERY_BROKER_URL=redis://redis:6379/0
          - CELERY_RESULT_BACKEND=redis://redis:6379/0
        depends_on:
          - web
          - redis
    
      nginx:
        image: nginx:1.15-alpine
        ports:
           - "80:80"
           - "443:443"
        volumes:
          - ./server/nginx:/etc/nginx/conf.d
          - ./server/certbot/conf:/etc/letsencrypt
          - ./server/certbot/www:/var/www/certbot
        command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'"
        depends_on:
          - web
    
      certbot:
        image: certbot/certbot
        volumes:
          - ./server/certbot/conf:/etc/letsencrypt
          - ./server/certbot/www:/var/www/certbot
        entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    
      redis:
        image: redis:6-alpine
        restart: always
        ports:
          - 6379:6379
          
      # HOW TO SET REDIS PASSWORD VIA ENVIRONMENT VARIABLE
      # https://stackoverflow.com/questions/68461172/docker-compose-redis-password-via-environment-variable
    
      dashboard:
        build: .
        command: celery --app tasks.celery flower --port=5555 --broker=redis://redis:6379/0
        ports:
          - 5556:5555
        environment:
          - CELERY_BROKER_URL=redis://redis:6379/0
          - CELERY_RESULT_BACKEND=redis://redis:6379/0
        depends_on:
          - web
          - redis
          - worker
    

    Also sharing my Dockerfile JUST IN CASE,

    # FOR FRONT-END DEPLOYMENT... (REACT)
    FROM node:16-alpine as build-step
    WORKDIR /app
    ENV PATH /app/web/node_modules/.bin:$PATH
    COPY web ./web
    WORKDIR /app/web
    RUN yarn install
    RUN yarn build
    
    # FOR BACK-END DEPLOYMENT... (FLASK)
    FROM python:3.10.4-slim
    WORKDIR /
    # Don't forget "--from"! It acts as a bridge that connects two seperate stages
    COPY --from=build-step app ./app
    WORKDIR /app
    RUN apt-get update && apt-get install -y python3-pip python3-dev mesa-utils libgl1-mesa-glx libglib2.0-0 build-essential libssl-dev libffi-dev redis-server
    COPY server ./server
    WORKDIR /app/server
    RUN pip3 install -r ./requirements.txt
    # Pretty much pass everything in the root folder except for the client folder, as we do NOT want to overwrite the pre-generated client folder that is already in the ./app folder
    # THIS IS CALLED MULTI-STAGE BUILDING IN DOCKER
    EXPOSE 5000
    

    All the notes I made while resolving this problem:

    '''
    TIPS & TRICKS
    -------------
    UPDATED ON: 2023-02-11
    
    LAST EDITED BY:
    WONMO "JOHN" SEONG,
    LEAD DEV. AND THE CEO OF HAVIT
    ----------------------------------------------
    HOW TO INSTALL DOCKER-COMPOSE ON DIGITALOCEAN VPS:
    https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-22-04
    
    DOCKERIZE FLASK + CELERY + REDIS APPLICATION WITH DOCKER-COMPOSE:
    https://nickjanetakis.com/blog/dockerize-a-flask-celery-and-redis-application-with-docker-compose
    https://testdriven.io/blog/flask-and-celery/ <-- PRIMARILY USED THIS TUTORIAL
    
    CELERY VS. GUNICORN WORKERS:
    https://stackoverflow.com/questions/24317917/difference-between-celery-and-gunicorn-workers
    1. Gunicorn solves concurrency of serving HTTP requests - this is "online" code where each request triggers a Django view, which returns a response. Any code that runs in a view will increase the time it takes to get a response to the user, making the website seem slow. So long running tasks should not go in Django views for that reason.
    2. Celery is for running code "offline", where you don't need to return an HTTP response to a user. A Celery task might be triggered by some code inside a Django view, but it could also be triggered by another Celery task, or run on a schedule. Celery uses the model of a worker pulling tasks off of a queue, there are a few Django compatible task frameworks that do this. I give a write up of this architecture here.
    
    CELERY, GUNICORN, AND SUPERVISOR:
    https://medium.com/sightwave-software/setting-up-nginx-gunicorn-celery-redis-supervisor-and-postgres-with-django-to-run-your-python-73c8a1c8c1ba
    
    DEPLOY GITHUB REPO ON DIGITALOCEAN VPS USING SSH KEYS:
    https://medium.com/swlh/how-to-deploy-your-application-to-digital-ocean-using-github-actions-and-save-up-on-ci-cd-costs-74b7315facc2
    
    COMANDS TO RUN ON VPS TO CLONE GITHUB REPO (WORKS ON BOTH PRIVATE AND PUBLIC REPOS):
    1. Login as root
    2. Set up your credentials (GitHub SSH-related) and run the following commands:
        - apt-get update
        - apt-get install git
        - mkdir ~/github && cd ~/github
        - git clone [email protected]:wonmor/HAVIT-Central.git
    3. To get the latest changes, run git fetch origin
    
    HOW TO RUN DOCKER-COMPOSE ON VPS:
    https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-22-04
    1. Login as root
    2. Run the following commands:
        - cd ~/github/HAVIT-Central
        - docker compose up --build -d // builds and runs the containers in detached mode
            OR docker compose up --build -d --remove-orphans // builds and runs the containers in detached mode and removes orphan containers
        - docker compose ps // lists all running containers in Docker engine.
    3. To stop the containers, run:
        - docker-compose down
    
    HOW TO SET UP NGINX ON UBUNTU VPS TO PROXY PASS TO GUNICORN ON DIGITALOCEAN:
    https://www.datanovia.com/en/lessons/digitalocean-initial-ubuntu-server-setup/
    https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04
    https://www.datanovia.com/en/lessons/digitalocean-how-to-install-nginx-and-ssl/
    
    CAPROVER CLEAN/REMOVE ALL PREVIOUS DEPLOYMENTS:
    docker container prune --force
    docker image prune --all
    
    FORCE MERGE USING GIT:
    git reset --hard origin/main
    
    NGINX - REDIRECT TO DOCKER CONTAINER:
    https://gilyes.com/docker-nginx-letsencrypt/
    https://github.com/nginx-proxy/acme-companion
    https://github.com/nginx-proxy/acme-companion/wiki/Docker-Compose
    https://github.com/evertramos/nginx-proxy-automation
    https://github.com/buchdag/letsencrypt-nginx-proxy-companion-compose
    https://testdriven.io/blog/dockerizing-flask-with-postgres-gunicorn-and-nginx/
    https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71 <--- THIS IS THE BEST TUTORIAL
    Simply run docker-compose up and enjoy your HTTPS-secured website or app.
    Then run chmod +x init-letsencrypt.sh and sudo ./init-letsencrypt.sh.
    
    VVIP: HOW TO RUN THIS APP ON VPS:
    1. Login as root, run sudo chmod +x init_letsencrypt.sh
    2. Now for the bit… that tends to go wrong. Navigate into your remote project folder, and run the initialization script (Run ./<Script-Name>.sh on Terminal). First, docker will build the images, and then run through the script step-by-step as described above. Now, this worked first time for me while putting together the tutorial, but in the past it has taken me hours to get everything set up correctly. The main problem was usually the locations of files: the script would save it to some directory, which was mapped to a volume that nginx was incorrectly mapped to, and so on. If you end up needing to debug, you can run the commands in the script yourself, substituting variables as you go. Pay close attention to the logs — nginx is often quite good at telling you what it’s missing.
    3. If all goes to plan, you’ll see a nice little printout from Lets Encrypt and Certbot saying “Congratulations” and your script will exit successfully.
    
    HOW TO OPEN/ALLOW PORTS ON DIGITALOCEAN:
    https://www.digitalocean.com/community/tutorials/opening-a-port-on-linux
    sudo ufw allow <PORT_NUMBER>
    
    WHAT ARE DNS RECORDS?
    https://docs.digitalocean.com/products/networking/dns/how-to/manage-records/
    PS: Highers the TTL, the longer it takes for the DNS record to update.
    But it will be cached for longer, which means that there will be less load on the DNS server.
    
    TIP: MAKE SURE YOU SET UP THE CUSTOM NAMESPACES FOR DIGITALOCEAN ON GOOGLE DOMAINS:
    https://docs.digitalocean.com/tutorials/dns-registrars/
    
    DOCKER SWARM VS. DOCKER COMPOSE:
    The difference between Docker Swarm and Docker Compose is that Compose is used for configuring multiple containers in the same host. Docker Swarm is different in that it is a container orchestration tool. This means that Docker Swarm lets you connect containers to multiple hosts similar to Kubernetes.
    
    Cannot load certificate /etc/letsencrypt/live/havit.space/fullchain.pem: BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory FIX:
    https://community.letsencrypt.org/t/lets-encrypt-with-nginx-i-got-error-ssl-error-02001002-system-library-fopen-no-such-file-or-directory-fopen-etc-letsencrypt-live-xxx-com-fullchain-pem-r/20990/5
    
    RUNNING MULTIPLE DOCKER COMPOSE FILES:
    https://stackoverflow.com/questions/43957259/run-multiple-docker-compose
    
    
    nginx: [emerg] open() "/etc/letsencrypt/options-ssl-nginx.conf" failed (2: No such file or directory) in /etc/nginx/conf.d/app.conf:20 FIX:
    https://stackoverflow.com/questions/64940480/nginx-letsencrypt-error-etc-letsencrypt-options-ssl-nginx-conf-no-such-file-o
    
    VVVIP: RESOLVE NGINX + DOCKER + LETSENCRYPT ISSUES!
    https://stackoverflow.com/questions/68449947/certbot-failing-acme-challenge-connection-refused
    Basically gotta remove all the HTTPS SSL-related stuff from both the docker-compose.yml and the nginx.conf file. 
    Then run the init-letsencrypt.sh script. Then add the HTTPS SSL-related stuff back to both the docker-compose.yml and the nginx.conf file. 
    Then run docker-compose up -d --build. Then run the init-letsencrypt.sh script again.
    '''
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search