Looking for the certbot equivalent to this old /opt/letsencrypt command


#1

I have a script which goes through all of my domains and renews them with this as the renew command:

/opt/letsencrypt/letsencrypt-auto certonly --agree-tos --renew-by-default --standalone --text -vvvvvv --preferred-challenges http-01 --http-01-port 54321 -d #{domain} -d #{www_domain}

However I recently changed servers and now use certbot, but my renew script is now broken because obviously there is no /opt/letscnrypt.

Anybody know what the certbot equivalent of the command above is please?


#2

Replace letsencrypt-auto with certbot. Done.

Though really, you shouldn’t be using --renew-by-default. Once you’ve successfully obtained the cert, a simple daily certbot renew should be all you need.


#4

Looks like it should be this:

certbot certonly --agree-tos --standalone --text -vvvvvv --preferred-challenges http --http-01-port 54321 -d #{domain} -d #{www_domain}

Thanks for your help :slight_smile:


#5

Except it shouldn’t, as noted above. And if you’re running this as a cron job, you should probably use the full path to certbot.


#6

This?

/usr/bin/certbot renew http --http-01-port 54321 -d #{domain} -d #{www_domain}


#7

No, simply /usr/bin/certbot renew. The http port, domains, and all the other details are saved in the configuration when you first obtain the cert.


#8

I am using HAProxy, so I need the .pems to be moved, hence using this script:

class CertChecker

  require "openssl"

  def initialize(report_all=true)
    # When 'report_all' above is set to false will
    # only report when there are any failures.
    @report = report_all
    @msgs = []
    @days_window = 30
    @domains_to_check = %w[
                  domain.org
                  domain.com
                  etc.com
                ]
  end

  def do_checks
    check_domains
    prepare_results
    # results
  end

  def check_domains
    @total_count = 0
    @updated_count = 0
    @not_updated_count = 0
    @fail_count = 0
    @failed_domains = []
    
    @domains_to_check.each do |domain|
      @msgs << "Starting check for #{domain} \n" 
      www_domain = "www.#{domain}"
      renew_command = "certbot certonly --agree-tos --standalone --text -vvvvvv --preferred-challenges http --http-01-port 54321 -d #{domain} -d #{www_domain}"
      raw = File.read "/etc/letsencrypt/live/#{domain}/fullchain.pem"
      cert = OpenSSL::X509::Certificate.new raw
      expires = cert.not_after
      days_remaining = (expires - Time.now).to_i / (60 * 60 * 24)
      
      if days_remaining > @days_window
        @msgs << "The certificate for #{domain} is up to date (#{days_remaining} days remaining). \n"
      else
        create_cert(domain, command=renew_command)
      end
      
      @msgs << "--------------------------------\n"
      @total_count += 1
      sleep 1
    end
  end
  
  def create_cert(domain, command)
    @msgs << "The certificate for #{domain} is about to expire, starting Let's Encrypt renewal script... \n"
    if system(command)
      @updated_count += 1
      @msgs << "Certificate creation successful for #{domain}"
      create_combined_file(domain)
      reloading_haproxy(domain)
    else
      @fail_count += 1
      @msgs << "Certificate creation failed for #{domain}"
      @failed_domains << domain
    end
  end
  
  def create_combined_file(domain)
    @msgs << "Creating combined file at /etc/haproxy/certs/#{domain}.pem with latest certs... \n"
    fullchain = File.read("/etc/letsencrypt/live/#{domain}/fullchain.pem")
    privkey = File.read("/etc/letsencrypt/live/#{domain}/privkey.pem")
    File.open("/etc/haproxy/certs/#{domain}.pem", "w") do |f|
      f.write(fullchain)
      f.write(privkey)
    end
    @msgs << "Finished creating combined_file... \n"
  end
  
  def reloading_haproxy(domain)
    @msgs << "Reloading HAProxy \n"
    system("/usr/sbin/service haproxy reload")
    @msgs << "Renewal process finished for #{domain} \n"
  end


  # def get_ip(domain)
  #   IPSocket::getaddress(domain)
  # rescue
  #   nil
  # end

  def prepare_results
    if @fail_count > 0
      @msgs << ""
      @msgs << "WARNING #{@fail_count} FAILED DOMAINS!"
      @msgs << ""
      @msgs << @failed_domains unless @failed_domains.empty?
      @msgs << ""
      @msgs << "WARNING #{@fail_count} FAILED DOMAINS!"
      @msgs << "Total checked: #{@total_count}"
    elsif @report
      @msgs <<  "Success! All domains checked and passed"
    end
  end

  def results
    if @fail_count > 0 || @report
      @msgs
    end
  end


end

checker = CertChecker.new
checker.do_checks
results = checker.results
if results
  results.each do |r|
    puts r
  end
end

Is there a better/simpler way to get the same thing done?

The reason we use post 54321 is because port 80 is used by HAProxy and so running certbot renew alone would report Attempting to renew cert (domain.com) from /etc/letsencrypt/renewal/domain.com.conf produced an unexpected error: Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.


#9

Yes, very much so. Almost everything in that script is something certbot (and letsencrypt before it) already does. As to creating the combined file and putting it in the right place, check out the certbot docs on the --deploy-hook flag. It could be as simple as --deploy-hook "cat fullchain.pem privkey.pem > domain.pem && service haproxy reload", or you could write a small script to accomplish only those two things and use --deploy-hook /path/to/script. But whichever way you do it, you’d only need to enter that option the first time you obtain the cert. For that, you’d do (adding whatever other options are needed)

certbot certonly --agree-tos --standalone --http-01-port 54321 -d blah -d blah --deploy-hook /path/to/hook

Once certbot obtains the cert, all those options are saved. Your renew cronjob, then, just needs to look like

3 */12 * * * root /usr/bin/certbot renew -q

There may be other ways around this (like using DNS validation), but that’s easy enough to understand; I figured it was something along those lines.

…but this shouldn’t happen–all the options should be saved in the renewal .conf file.


#10

Thanks @danb35! :+1:

I think the script below should be ok now - posting here as it might help someone in future.

The only other thing I did was manually check each configuration file in /etc/letsencrypt/renewal/ (some of them needed the port updating).

class CertChecker

  require "openssl"

  def initialize(report_all=true)
    # When 'report_all' above is set to false will
    # only report when there are any failures.
    @report = report_all
    @msgs = []
    @days_window = 30
    @domains_to_check = %w[
                  domain.org
                  domain.com
                ]

    @list_of_domains_requiring_renewal = []
    @renew_command = "/usr/bin/certbot renew -q"
  end

  def do_checks
    check_domains
    if @list_of_domains_requiring_renewal.size > 0
      create_certs 
      create_combined_files
      reload_haproxy
    end
    prepare_results
    # results
  end

  def check_domains
    @total_count = 0

    @domains_to_check.each do |domain|
      @msgs << "Starting check for #{domain} \n" 
      raw = File.read "/etc/letsencrypt/live/#{domain}/fullchain.pem"
      cert = OpenSSL::X509::Certificate.new raw
      expires = cert.not_after
      days_remaining = (expires - Time.now).to_i / (60 * 60 * 24)

      if days_remaining > @days_window
        @msgs << "The certificate for #{domain} is up to date (#{days_remaining} days remaining). \n"
      else
        @list_of_domains_requiring_renewal << domain
      end

      @msgs << "--------------------------------\n"
      @total_count += 1
    end
  end

  def create_certs
    @msgs << "Starting Let's Encrypt renewal script... \n"
    if system(@renew_command)
      @msgs << "Certificate creation output..."
    else
      @msgs << "Certificate creation failed output..."
    end
  end

  def create_combined_files
    @msgs << "Creating combined files... \n"
    @list_of_domains_requiring_renewal.each do |domain|
      fullchain = File.read("/etc/letsencrypt/live/#{domain}/fullchain.pem")
      privkey = File.read("/etc/letsencrypt/live/#{domain}/privkey.pem")
      File.open("/etc/haproxy/certs/#{domain}.pem", "w") do |f|
        f.write(fullchain)
        f.write(privkey)
      end
      @msgs << "Finished creating combined_file for #{domain}... \n"
    end
  end

  def reload_haproxy
    @msgs << "Reloading HAProxy \n"
    system("/bin/systemctl reload haproxy.service")
    @msgs << "Renewal process finished. \n"
  end

  def prepare_results
    if @list_of_domains_requiring_renewal.size > 0
      @msgs << "Total number of domains: #{@domains_to_check.size}"
      @msgs << ""
      @msgs << "Total number of domains that required renewal: #{@list_of_domains_requiring_renewal.size}"
      @msgs << ""
      @msgs << "Domains that required renewal: #{@list_of_domains_requiring_renewal}"
    else
      @msgs <<  "Success! No domains required renewing"
    end
  end

  def results
    @msgs
  end
end

checker = CertChecker.new
checker.do_checks
results = checker.results
if results
  results.each do |r|
    puts r
  end
end

(If you or anyone else notices any mistakes please let me know)


#11

Just a quick update, I’m getting emails like the below, though when I go to the domain and check the cert it is valid until January (Expires: Monday, 14 January 2019 at 23:44:18 Greenwich Mean Time) - any idea why this may be happening?

Hello,

Your certificate (or certificates) for the names listed below will expire in 19 days (on 13 Nov 18 21:40 +0000). Please make sure to renew your certificate before then, or visitors to your website will encounter errors.

We recommend renewing certificates automatically when they have a third of their
total lifetime left. For Let’s Encrypt’s current 90-day certificates, that means
renewing 30 days before expiration. See
https://letsencrypt.org/docs/integration-guide/ for details.

domains…

The email is sent from expiry@letsencrypt.org


#12

See this page:

Does “sudo certbot certificates” show that any of your certificates are expiring?