ULEM: Uberspace Let’s Encrypt Manager

INFO: Please update to 0.3 – otherwise renewal will not work.


I recently started moving my websites to uberspace. Since Let’s Encrypt is now available for most of the hosts (all new ones basically), I made great use of this feature. As I was migrating however, I ran into an ugly issue. Here is what happened:

I was subsequently adding more and more hosts, so to enable https on those hosts, I kept adding domains to the let’s encrypt cli.ini (domains variable). This lead to having one chaotic set of domains that were all linked to the first domain in the list. After some iterations, I could not add any new domains, because I was caught in the rate-limit, that Let’s Encrypt imposes on their users during this public beta.

I started writing scripts to manage multiple sets, so I can now proudly present the ULEM: Uberspace Let’s Encrypt Manager. It consists of 3 shell scripts and 2 .ini files.

Package contents

cli.ini.example (Please rename to cli.ini)
This is basicaly the cli.ini that was generated by uberspace-letsencrypt. Please note that I uncommented that the TOS has been accepted – also you need to set a proper email address and paths as set in the real ~/.config/letsencrypt/cli.ini. If you have made any additional changes, please also transfer it to this file. You may notice, that it is lacking the domains part. This is intended.

sets.ini.example (Please rename to sets.ini)
Here you can specify different sets, those will result into different certificates which you can then install in the load-balancer.

name_of_set = domain1,domain2,domain3

See the file for an example. Please note that the first domain in that list will be also the name of the folder in ~/.config/letsencrypt/live/ folder.

With this script, you can get the certificate for a certain set rather than all domains at once. This has the advantage, that you have manageable sets of domains connected to one certificate rather than all your domains on a single certificate.

Usage: ./ulem_request_set.sh name_of_set

This will move the original cli.ini to cli.ini.ulem, create a new one based on this package’s cli.ini and the selected set, run letsencrypt certonly and restore the original cli.ini.
You want to do this for every set, that you add or change. You wil have to select to overwrite your certificates for this script to work properly.

This script is a shortcut to upload the generated certificate with uberspace-prepare-certificate. The advantage of using this script is, that you can specify the set that you just created or updated.

Usage: ./ulem_activate_set.sh name_of_set

You want to run this every time you ran ./ulem_request_set.sh name_of_set

This script will check the certificates on the first domain of every set and automatically renew it, if the certificate expires in 28 days or less. To do that, just execute ./ulem_renew_all.sh and it will do the rest for you. If you don’t want it to renew a specific set, you can comment it out in sets.ini with a #.
It might be a good idea to run this script daily, i.e. via cron. If the certificates don’t have to be renewed, nothing will be done.

30 4 * * * /path/to/ulem_renew_all.sh

Basically ulem_request_set.sh but set to be non-interactive, so the cronjob will work without user input.

If you can’t be bothered to find the sets.ini, you can run this command to see the names of your sets.

Download and changelog

  • ulem-0.3.tar.bz2 (sig) (2016-04-07)
    Maintenance Release:
    • Changed .ini filenames to .ini.example to avoid overwriting of your config files on update
    • Removed –agree-dev-preview from configuration examples
    • Changed renew routine to adapt missing letsencrypt-renewer script which was previously provided

  • ulem-0.2b.tar.bz2 (sig) (2016-02-28)
    Bugfix Release:
    • Added LC_ALL=”en_US” as per comment’s suggestion.

  • ulem-0.2a.tar.bz2 (sig) (2016-02-03)
    Bugfix Release:
    • This is Bugfix Release 0.1a all over again. Turns out I missed one spot. This finally fixes this potential issue everywhere.

  • ulem-0.2.tar.bz2 (sig) (2016-02-02)
    Maintenance Release:
    • Changed uberspace-prepare-certificate to uberspace-add-certificate
    • Handling of renewal changed slightly. Since letsencrypt-renewer renews all domain-sets, ulem_renew_all.sh will only call letsencrypt-renewer once if any domain needs updating.

  • ulem-0.1b.tar.bz2 (sig) (2016-02-01)
    Bugfix Release:
    • Script would fail on non-English environments for months that are abbreviated differently. Fixed by adding LANG=”en_US” to the curl call. (Comment #4 has details)

  • ulem-0.1a.tar.bz2 (sig) (2016-01-28)
    Bugfix Release:
    • Set names could collide if you had set1 and set1_subset. Fixed by adding a space to the grep. Whitespace after set-name in sets.ini is required.

  • ulem-0.1.tar.bz2 (sig) (2016-01-27)
    Initial Release

Upcoming changes

  • Make verbosity configurable for ulem_renew_all.sh
  • Remove the need to maintain a sparate cli.ini and take everything from the supplied one, to be used as command line parameters

If you have any questions, found bugs or just want to say thank you, the comments or my jabber at bock@openjabber.org are the best way to be heard. Don’t be shy if there is something unclear, this is the first time I am trying to describe my little helpers and make it useable for others.


Free for all, attribution and beer on congress appreciated.

19 thoughts on “ULEM: Uberspace Let’s Encrypt Manager”

  1. Digging into the letsencrypt executable some more, I have just seen that I could have used –domain as a parameter instead of building a cli.ini for every set. I will correct that eventually, but since I’d need to tamper with the cli.ini anyways (to remove the domains) this has low priority.

  2. Hi Christian,

    thanks for your script. It would be realy usefull because i also got a set of domains to manage.

    But I got a problem… if i run the “ulem_renew_all.sh” i got a syntaxerror and my knowlege in bash isn’t great…

    Do you got any suggestions?!

    Thanks and bye!

    2016-02-01 21:09:06 - domain.tld: Domain test 1 started
    2016-02-01 21:09:06 - domain.tld: ... Running curl -v domain.tld
    2016-02-01 21:09:06 - domain.tld: ... Certificate expiration date: Mai 01 17:58:00 2016 GMT
    date: ungültiges Datum „Mai 01 17:58:00 2016 GMT“
    expr: Syntaxfehler
    expr: Syntaxfehler
    2016-02-01 21:09:06 - domain.tld: ... Expiration date is days in the Future.
    2016-02-01 21:09:06 - domain.tld: ... MIN_DAYS_FOR_RENEWAL is set to 30
    ./ulem_renew_all.sh: Zeile 30: [: -le: Einstelliger (unärer) Operator erwartet.
    2016-02-01 21:09:06 - domain.tld: ... Renew is not needed

    1. Most likely the problem occurs, because date is expecting an English date string, but the LANG variable is set to de_DE on your uberspace.

      Please replace this line:
      EXPIRYDATE=`curl -v https://$MAINDOMAIN 2>&1 | grep "expire date" | cut -d":" -f2- | cut -d" " -f2-`

      with this:
      EXPIRYDATE=`LANG="en_US" curl -v https://$MAINDOMAIN 2>&1 | grep "expire date" | cut -d":" -f2- | cut -d" " -f2-`

      Miraculously this didn’t happen when I tested it, because April is the same word in German and in English. If this line fixes your problem, I’d be happy to hear back from you. I also uploaded a new version (0.1b) which fixes this issue.

      1. Hi Christian,
        thank you for your quick response!
        And yes, that was the problem… everything works now!

        Regards, Matthias

      1. Eh – won’t you? It would be MUCH easier to download it and keep it up to date on uberspace. AND: it would be easier to make contributions it you want that.

        1. I do not like using services that are out of my control. Maybe I will set up a private git repository for it someday&emdash;but I do not see the need right now. Once letsencrypt development is somewhat done, I doubt this script has to change a lot. Further changes might be additional features. I think a mailinglist for new versions is more apropriate. Or optional update check.

  3. Hi!

    I also had the problem with the current locale. Setting LANG was not enough, I also had to set LC_ALL:
    EXPIRYDATE=`LC_ALL=”en_US” LANG=”en_US” curl -v….
    worked for me. Could you add the LC_ALL for the next version? Thanks for this great scripts!

    Kind Regards, Daniel

  4. Hello Christian!

    My first set did run well, my second won’t. On executing ulem_request_set.sh domain2 I get:

    domains = do-mail.tld,www.do-main.tld
    An unexpected error occurred:
    TypeError: coercing to Unicode: need string or buffer, NoneType found
    Please see the logfiles in /home/$USER/.local/share/letsencrypt/logs for more details.

    Any idea? Cheers.

    1. That Error is from the letsencrypt script. My first guess is that you have a non-ascii character (possibly invisible) in that line. Remove the line and type it up again. If it still doesn’t work, contact me via xmpp or email and send me your sets.ini

    1. I removed that in the example .ini files. Uberspace has updated the letsencrypt client without notice.

  5. Checking if a cert is expiring can be done much easier, this gives back a boolean value if the Cert of domain.tld will expire in 28 days or not.
    openssl x509 -checkend 2419200 -in ~/.config/letsencrypt/live/domain.tld/cert.pem

    1. Sure, there are many ways. But checking against the loadbalancer ensures that the new certificate has successfully been installed – and if not: tries again.

  6. Hi!

    I was going to try handling LE certificates your way but then I heard about the new LE wildcard certificates.
    Will you (have to) adapt your code to allow for LE wildcard certificates once uberspace supports it?
    I love the idea that your approach allow to separate certificates and so they don’t show up in the cert info all at once (good for privacy).
    Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *