wmnnd / nginx-certbot

Boilerplate configuration for nginx and certbot with docker-compose
MIT License
3.15k stars 1.17k forks source link

Multiple domains don't really work well! #139

Open masikh opened 1 year ago

masikh commented 1 year ago

My proposed fix is below:

Main difference: Use each item from the array of domains...

#!/bin/bash

domains=(www.domain.com cdn.domain.com api_domain.com registry.domain.com www-registry.domain.com)
rsa_key_size=4096
data_path="./data/certbot"
email="admin@domain.com"              # Adding a valid address is strongly recommended
staging=1                             # Set to 1 if you're testing your setup to avoid hitting request limits

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

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

for domain in "${domains[@]}"
do
    echo "### Creating dummy certificate for ${domain} ..."
    path="/etc/letsencrypt/live/$domain"
    mkdir -p "$data_path/conf/live/$domain"
    docker-compose 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
done

echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo

for domain in "${domains[@]}"
do
    echo "### Deleting dummy certificate for ${domain} ..."
    docker-compose run --rm --entrypoint "\
      rm -Rf /etc/letsencrypt/live/$domain && \
      rm -Rf /etc/letsencrypt/archive/$domain && \
      rm -Rf /etc/letsencrypt/renewal/$domain.conf" certbot
    echo
done

for domain in "${domains[@]}"; do
    echo "### Requesting Let's Encrypt certificate for ${domain} ..."
    # 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 run --rm --entrypoint "\
      certbot certonly --webroot -w /var/www/certbot \
        $staging_arg \
        $email_arg \
        -d $domain \
        --rsa-key-size $rsa_key_size \
        --agree-tos \
        --force-renewal" certbot
    echo
done

echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload
dontic commented 1 year ago

Worked like a charm, thanks @masikh !

dietmar commented 1 year ago

I disagree, the original script works very well for multiple domains.

When I first read the script, I was also a bit surprised by this mkdir statement:

domains=(example.org www.example.org)
rsa_key_size=4096
data_path="./data/certbot"
# ...
mkdir -p "$data_path/conf/live/$domains"

But this is actually fine, domains is a Bash array, and using $domains will give you the first element of the array.

Try this:

echo "The value of domains is: $domains"

It will print The value of domains is: example.org, without mentioning the second domain www.example.org. So the mkdir command is fine.

Now as far as the actual certbot command is concerned, the original script is IMHO much better than @masikh's version above, because it only creates one certificate, which is valid for all the given domains. The above version on the other hand creates a separate certificate for every domain.

https://eff-certbot.readthedocs.io/en/stable/using.html#certbot-command-line-options says:

-d DOMAIN, --domains DOMAIN, --domain DOMAIN
                        Domain names to include. For multiple domains you can
                        use multiple -d flags or enter a comma separated list
                        of domains as a parameter. All domains will be
                        included as Subject Alternative Names on the
                        certificate. The first domain will be used as the
                        certificate name, unless otherwise specified or if you
                        already have a certificate with the same name. In the
                        case of a name conflict, a number like -0001 will be
                        appended to the certificate name. (default: Ask)
hguilla commented 1 year ago

The problem seem to be that what @dietmar is saying works only for bash. In bash I get the same result that you got executing:

echo "The value of domains is: $domains"

But when executing the same 2 commands (including the same variable assignment) in zsh I got:

The value of domains is: example.org www.example.org

kzkvv commented 9 months ago

Met the same issue today on Ubuntu 20.0.4. I defined 2 domains in array, but all commands were completed for first domain only. I like the solution by masich more, because it's more intuitive, it's easier to read the code with explicitly defined cycles, and ofk it's more convinient to debug it.

ip520011 commented 3 months ago

Each has its own advantages, @masich is solution allows ssl for multiple different primary domains,@dietmar can only be configured for multiple second-level domain names under a single primary domain name