Sharing a Ubuntu Certbot Script

Made this during my learning experience and figured who knows maybe someone else could use it at some point so here it is!

Its only for ubuntu 22.04 running nginx since thats what I use across the board for webservers but feel free to modify!
At a minimum change the certbot_email variable to your email and change out the domain names.
I build www subdomains for all my sites because of how many people still use it so if the domain name is not a subdomain for example example.com it will automatically look for running certbot against www.example.com in addition to and at the same time as example.com.
There are also variables for using the testing certs which is the default option for safety and expanding certificates which is off by default.

#!/bin/bash

# Check the OS version
if [[ $(lsb_release -rs) != "22.04" ]]; then
    echo
    echo "----------------------"
    echo
    echo "This script is only designed for and tested on Ubuntu 22.04, so modify at your own risk."
    echo
    echo "----------------------"
    echo
    exit 1
fi

# Variables
CERTBOT_EMAIL="YOUR EMAIL HERE"
DOMAINS=(
    "DOMAIN1.COM"
    "DOMAIN2.COM"
    "DOMAIN3.COM"
    "DOMAIN4.COM"
)
testingcert=true # Set this to false to run without --test-cert
expandcert=false # Set this to true to enable --expand option
FAILED_DOMAINS=() # Array to store failed domains
EXISTING_CERTS=() # Array to store domains that already have certs
SUCCESS_DOMAINS=() # Array to store domains that succeeded

# Function to check if a certificate exists for a domain
certificate_exists() {
    domain=$1
    # Use Certbot to list certificates and grep to search for the domain
    sudo certbot certificates | grep -Eq "Domains: $domain"
    return $?
}

# Command options for testing and expanding certificates
CERTBOT_OPTIONS=""
if [ "$testingcert" = true ]; then
    CERTBOT_OPTIONS="$CERTBOT_OPTIONS --test-cert"
fi
if [ "$expandcert" = true ]; then
    CERTBOT_OPTIONS="$CERTBOT_OPTIONS --expand"
fi

# Install snapd and manage certbot installation
echo
echo "----------------------"
echo
echo "Installing snapd and managing Certbot installation..."
echo
echo "----------------------"
echo
sudo apt install snapd
if [ $? -ne 0 ]; then
    echo
    echo "----------------------"
    echo
    echo "Failed to install snapd."
    echo
    echo "----------------------"
    echo
    exit 1
fi

sudo apt-get remove certbot -y
if [ $? -ne 0 ]; then
    echo
    echo "----------------------"
    echo
    echo "Failed to remove existing Certbot installation."
    echo
    echo "----------------------"
    echo
    exit 1
fi

sudo snap install --classic certbot
if [ $? -ne 0 ]; then
    echo
    echo "----------------------"
    echo
    echo "Failed to install Certbot via Snap."
    echo
    echo "----------------------"
    echo
    exit 1
fi

# Ensure Certbot is accessible globally
sudo ln -sf /snap/bin/certbot /usr/bin/certbot

# Process each domain
for domain in "${DOMAINS[@]}"; do
    if certificate_exists $domain; then
        echo
        echo "----------------------"
        echo
        echo "$domain: A certificate already exists. Skipping Certbot for this domain."
        echo
        echo "----------------------"
        echo
        EXISTING_CERTS+=("$domain")
        continue
    fi

    # Check if the domain is a subdomain (contains more than one dot)
    if [[ $(echo $domain | grep -o "\." | wc -l) -eq 1 ]]; then
        # Domain is not a subdomain, add www
        domain_with_www="www.$domain"
        cert_domains="-d $domain -d $domain_with_www"
    else
        # Domain is a subdomain, do not add www
        cert_domains="-d $domain"
    fi

    echo
    echo "----------------------"
    echo
    echo "Performing dry run for $domain..."
    echo
    echo "----------------------"
    echo
    sudo certbot certonly -v --nginx --dry-run $cert_domains --email $CERTBOT_EMAIL --agree-tos --non-interactive $CERTBOT_OPTIONS

    if [ $? -eq 0 ]; then
        echo
        echo "----------------------"
        echo
        echo "Dry run successful for $domain, proceeding with actual certificate request."
        echo
        echo "----------------------"
        echo
        sudo certbot -v --nginx $cert_domains --email $CERTBOT_EMAIL --agree-tos --non-interactive $CERTBOT_OPTIONS
        if [ $? -eq 0 ]; then
            echo
            echo "----------------------"
            echo
            echo "Certificate successfully obtained for $domain."
            echo
            echo "----------------------"
            echo
            SUCCESS_DOMAINS+=("$domain")
        else
            echo
            echo "----------------------"
            echo
            echo "Failed to obtain a certificate for $domain."
            echo
            echo "----------------------"
            echo
            FAILED_DOMAINS+=("$domain")
        fi
    else
        echo
        echo "----------------------"
        echo
        echo "Dry run failed for $domain, skipping actual certificate request."
        echo
        echo "----------------------"
        echo
        FAILED_DOMAINS+=("$domain")
    fi
done

# Function to sort and print domains in a two-column format
sort_and_print_domains() {
    local -n domains=$1  # Use nameref for indirect array handling
    declare -A seen  # associative array to track seen domains

    echo "Domains                        | www Variants"
    echo "-------------------------------|----------------------------------"

    for domain in "${domains[@]}"; do
        # Check if the domain is a root domain with exactly two segments
        if [[ "$domain" =~ ^[^.]+\.[^.]+$ ]]; then
            # Prepare to print both the root and www version if not already processed
            local base_domain="$domain"
            local www_domain="www.$domain"

            # Check if either version has already been processed
            if [[ ${seen["$base_domain"]} == 1 || ${seen["$www_domain"]} == 1 ]]; then
                continue  # Skip to next if already handled
            fi

            # Print the domain and its www version if they haven't been marked as seen
            printf "%-30s | %-30s\n" "$base_domain" "$www_domain"
            seen["$base_domain"]=1
            seen["$www_domain"]=1
        else
            # If not a root domain, just print the domain if not already seen
            if [[ ${seen["$domain"]} != 1 ]]; then
                printf "%-30s | %-30s\n" "$domain" ""
                seen["$domain"]=1
            fi
        fi
    done
}

# Final report
echo
echo "----------------------"
echo
echo "Summary of Operations:"
echo
echo "----------------------"
echo
echo "Existing Certificates:"
echo
echo "----------------------"
echo
sort_and_print_domains EXISTING_CERTS
echo
echo "----------------------"
echo
echo "Certificates Successfully Obtained:"
echo
echo "----------------------"
echo
sort_and_print_domains SUCCESS_DOMAINS
echo
echo "----------------------"
echo
echo "Failed Certificate Requests:"
echo
echo "----------------------"
echo
sort_and_print_domains FAILED_DOMAINS
echo
echo "----------------------"
echo
2 Likes

You checked for specific OS version:

But you never checked for nginx [existence] ? ? ?

3 Likes

As Rudy has pointed out, You might add a function to test for nginx. Nice script.

At first glance, I would suggest enhancing the script with logging and error handling. Also #Comment the heck out of it to document the purpose and usage, etc.

Instead of running commands with sudo directly in the script, you can create a function or a separate script that handles running commands with root privileges. This is important for security and makes your script more flexible.

Nuff said... good job! script hound

3 Likes

I was going on 36 hours of no sleep lol cant believe I missed that lmfao... Thank you!!! Gonna fix and edit the post!!!

1 Like

When you say logging and error handling can you be more specific? Ill look into what you mean about sudo in scripts, but for the intent of this script it was meant to be edit once, and let it rip with a summary of operations at the end. Any suggestions on improvement though I will gladly listen!!

2 Likes

That Ubuntu version comes with snapd already installed (as do all modern Ubuntu). Why do you need to install it? See snapcraft install docs

It looks like you intend to run this repeatedly on same system. I see you check for an existing cert for example. But, won't the step that uninstalls any apt-installed Certbot fail after the first run?

2 Likes

This is puzzling me too.. Some parts of the script look like it should be run only once, but from other parts I get the feeling it's meant to run multiple times, also acting as some sort management system?

Frankly, I'm also not sure what the actual benefit of the script really is.

3 Likes

Currently updating it but if me sharing this is too much of a issue I can just take it down and repost it on my git... I figured it was certbot with LE so it might help someone in here but like I said if I am wrong I can take it down...

As for intent the script can be run as many times as you want without causing issues and is part of a 3 script series I made that sets up a fresh UB install with nginx, https and wireguard vpn. The UB server acts as a public facing reverse proxy handling termination of https passing the http traffic over the vpn to a separate internal reverse proxy. this setup helps people self host through cgnat or if they just want to hide their home ip or not do any port forwards. As for the snap stuff I am currently expanding the script to cover as low as UB16 and might in the near future cover RHEL as well but thats to be decided still. In every one of the scripts in the 3 part series they are all designed to be run as many times as you want without causing issues other than maybe the nginx one but that just renames the existing file to .bak and increments a number to the end according to how many .baks there already are. Eventually all of the scripts will be condensed into a single script with options to run either a full setup or individual components.

I guess part of my hope of posting it here was to see if anyone had any suggestions or ideas for improvements.

2 Likes