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
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
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:
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:
# LET'S ENCRYPT SSL EXAMPLE SETTINGS
# This include was copied from a functioning Certbot installation on Linux.
# Its presence makes the SSL certs issued by Certbot behave on <Windows>
# Intermediate configuration, tweak to your needs
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
I updated the VirtualHost declaration with the Include directive:
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/mod_socache_shmcb.so
# Secure (SSL/TLS) connections
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.
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
--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
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
hostname.tld.conf Certbot renewal file illustrates the changes were pushed through:
# Options used in the renewal process
account = lonstringofseeminglyrandomchars
authenticator = webroot
server = https://acme-v02.api.letsencrypt.org/directory
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!