Technology Solutions for Everyday Folks

Certbot on Windows: Automation Is Possible

A recent project gave me an opportunity to try out Certbot on Windows. As I've written about before, I've had an extensive journey with Certbot, at times in fairly 'non-standard' configurations, and Certbot on Windows is no different. Fortunately in my circumstance I am using Apache on Windows and not IIS as the latter requires a different approach (and it would appear a different ACME client built to interact with IIS).

I was able to follow the simple instructions for Certbot on Apache/Windows to get Certbot. I let Certbot install at the default location (on the C drive).

Obtaining a Certificate

Requesting a new certificate was really as 'trivial' as running certbot certonly --webroot from an elevated command prompt. I chose to use webroot as it was the 'least intrusive' way to handle the validation.

The first run on a new installation of Certbot presents additional prompts to 'register' and whatnot; subsequent invocations do not prompt in this way. The key prompts are the domain(s) for which to request a certificate and the webroot path, which in my case was on the D drive—the path of the DocumentRoot directive in the vhosts.conf, e.g. D:\project\files\web.

Within a couple seconds I had a new certificate available at the installation path for Certbot (in the C:\Certbot\live\ directory, a path structure similar to Certbot on Linux).

Certificate Installation Process

Using the webroot plugin doesn't automatically change your Apache conf files, so once I had a new certificate in place I needed to add this information to the vhosts.conf configuration file. In this case, it was more or less a matter of copying the non-SSL (port 80) VirtualHost declaration, changing it to port 443, and adding the SSL certificate information and extra configuration directives, like this example:

<VirtualHost hostname.tld:443>
    DocumentRoot "D:\project\files\web"
    ServerName hostname.tld
    ServerAlias www.hostname.tld

    SSLCertificateFile "C:\Certbot\live\hostname.tld\fullchain.pem"
    SSLCertificateKeyFile "C:\Certbot\live\hostname.tld\privkey.pem"

In testing I was having quite the time getting SSL to 'behave' on Apache/Windows out of the box. I would attempt to [re-]start Apache and get a critical error. The Apache error log wasn't terribly helpful, nor were the httpd.exe -t or httpd.exe -S configuration test commands. I only knew that something with the SSL directives wasn't playing nicely.

As a result, I copied a portion of the automatically-created Include /etc/letsencrypt/options-ssl-apache.conf file from an Apache/Linux box and used it as an include on Windows, which I named letsencrypt-ssl-apache.conf. Voila! So something in the default configs just doesn't quite play nice with Certbot certificates out of the box, but this config (also a Gist) is an example of how I made it behave:

# This include was copied from a functioning Certbot installation on Linux.
# Its presence makes the SSL certs issued by Certbot behave on <Windows>
SSLEngine               on
# Intermediate configuration, tweak to your needs
SSLProtocol             all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder     off
SSLSessionTickets       off
SSLOptions              +StrictRequire

I updated the VirtualHost declaration with the Include directive:

<VirtualHost hostname.tld:443>
    DocumentRoot "D:\project\files\web"
    ServerName hostname.tld
    ServerAlias www.hostname.tld

    Include conf/letsencrypt-ssl-apache.conf
    SSLCertificateFile "C:\Certbot\live\hostname.tld\fullchain.pem"
    SSLCertificateKeyFile "C:\Certbot\live\hostname.tld\privkey.pem"

This modification allowed Apache to load, but the error log indicated a suggestion/problem with the SSL cache, which I had not configured. Fortunately it was fairly straightforward and made the error go away while also improving server performance.

LoadModule socache_shmcb_module modules/


# Secure (SSL/TLS) connections
#Include conf/extra/httpd-ssl.conf
SSLSessionCache "shmcb:D:/path/to/logs/ssl_gcache_data(512000)"
SSLSessionCacheTimeout  300

Renewals & Buttoning Up

I always like to test the renewal on a fresh cert, so certbot renew --dry-run is a familiar command for me. As expected, a test renewal was successful, so the automated renewal process should behave without any problem. Hooray!

What About that Renewal Job?

I was curious how the renewal scheduled task would work, so I kept an eye on the scheduled tasks list. Certbot creates a scheduled task to run twice daily (at ~12 hour intervals with a random offset). I noticed, however, that over the first few days it was not actually ever triggering. Digging into the task details, I saw the issue pretty quickly.

The scheduled task is set to run as anyone in the Administrators group, but only if an administrator is logged on at the trigger time. Bummer. This would be a problem for my production environment, because Apache is running as a service and for the vast majority of time nobody is logged on to the server in question, so renewal actually being triggered would be hit or miss at best and that's not a plan.

Fortunately there's a relatively simple fix for this default behavior: we use a "headless" (e.g. designed to run such jobs, can't do console login, and can be a member of the Administrators group) service account. Change the scheduled task to run as this user, with saved credentials, and run whether or not someone is logged on. Thus far it seems to be triggering as expected, though it is too early to actually see a renewal process as (as I write this) 60 days have not yet passed since the initial certificate request and installation.

Restarting Apache

Since the renewal job will run with the webroot plugin, we need to use the --post-hook argument on the renewal command to D:\path\to\bin\httpd.exe -k restart. This could theoretically be accomplished with the --deploy-hook as well, but I chose --post-hook since a momentary Apache restart is not a service issue for end users due to traffic volume. There is also conflicting information whether not not --deploy-hook will behave with a direct command (versus a path to a script), and I didn't try that angle. For a high-volume service/site, --deploy-hook might be a more robust solution, in combination with a more defined renewal schedule than the defaults.

If you specified all of the arguments such as --webroot-path and --post-hook (or --deploy-hook) out of the gate (at original request), renewal modification is not necessary. I, however, needed to ensure both the webroot path and the post hook were added to the renewal configuration, necessitating a command line modification (recommended over manually editing the Certbot renewal configurations).

Modifying the renewal configuration is handled by manually running the renewal at the command line (first with a --dry-run):

certbot renew --cert-name hostname.tld --webroot-path "D:\project\files\web" --post-hook "D:\path\to\bin\httpd.exe -k restart" --dry-run

If the test is successful, running a 'forced' renewal to codify the changes is appropriate:

certbot renew --cert-name hostname.tld --webroot-path "D:\project\files\web" --post-hook "D:\path\to\bin\httpd.exe -k restart" --force-renewal

Examining the hostname.tld.conf Certbot renewal file illustrates the changes were pushed through:

# Options used in the renewal process
account = lonstringofseeminglyrandomchars
authenticator = webroot
server =
post_hook = D:\path\to\bin\httpd.exe -k restart
webroot_path = D:\project\files\web

Loading the production website in a browser you should see the new certificate being presented. I just checked the issue date to verify.

Easier Than Expected

Having skimmed some of the Certbot on Windows documentation in the past, I was a little skeptical about how it would work, or more appropriately under which circumstances it would work. For web services where the CSR isn't critical (e.g. things not IIS) and services that can be restarted from the command line, though, Certbot on Windows seems a viable option to automate the process. Definitely worth giving it a shot!