HTTPS for local development
Avishkar Autar · Jul 5 2024 · Web Technologies
How to local dev
Local developer environments can take many forms but for non-trivial web applications I’m still fond of Vagrant to spin up a VM that is “close enough” to production environments. Typically, I will create a Vagrant box and map a hostname to the IP of the box via an entry in the host system’s hosts file (I use vagrant-hostmap for this, as DHCP is used to avoid conflicts and that means the IP address change often).
Does HTTPS matter for local dev?
Probably not. I went down this particular rabbit hole as I was doing some experiments with server-side events and noticed there’s a limitation on open connection when not using HTTP/2.
So, I thought it would be a good idea to get HTTP/2 working in my local dev environment, which was using Apache and PHP. This is fairly straightforward to setup on Apache. However, the browser (Chrome) was not happy with. Currently, browsers only support HTTP/2 over TLS (h2) and there’s is no support for HTTP/2 Cleartext (h2c). There some good reasons for this.. but many of these reasons are addressing concerns on the public web, for other use-cases (e.g. local dev) there’s additional complexity to support h2 with minimal benefits.
Scripting
The code in the sections below is Bash for a provisioning script (bootstrap.sh
), intended to run when the Vagrant is provisioned. The code is such that the environment of the box is reproducible (after destroying and re-creating) and there is no need to re-adjust configuration on the host machine (comes into play when dealing with certificates).
The Vagrantfile
looks something like this:
$startScript = <<START_SCRIPT
sudo service apache2 restart
START_SCRIPT
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/mantic64"
config.vm.provision :shell, path: "localdev/bootstrap.sh"
config.vm.provision "shell", inline: $startScript, run: "always"
config.vm.network "private_network", type: "dhcp"
end
… with the bootstrap script and related assets (e.g. Apache conf files) within the localdev
folder.
Supporting h2: disable prefork
First, disable the prefork module, it doesn’t play well with HTTP/2:
sudo a2dismod mpm_prefork
In prefork, mod_http2 will only process one request at at time per connection. But clients, such as browsers, will send many requests at the same time. If one of these takes long to process (or is a long polling one), the other requests will stall.
Supporting h2: enable HTTP/2 modules and configuration
Enable modules and configuration for HTTP/2:
sudo a2enmod mpm_event
sudo a2enmod http2
sudo cp /vagrant/localdev/http2.conf /etc/apache2/conf-available/http2.conf
sudo a2enconf http2
The http2.conf
file contains the following:
<IfModule http2_module>
Protocols h2 h2c http/1.1
H2Direct on
</IfModule>
Required modules and configuration are now enabled. For a given VirtualHost
definition, you can add Protocols h2 h2c http/1.1
.
e.g.:
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
Protocols h2 h2c http/1.1
...
Supporting h2: minica
For HTTPS TLS certificate generation, minica is awesome and simple to use. However, it is only distributed as source, so you’ll need to install git to checkout the repo and golang to compile the source.
sudo apt-get -y install git
sudo apt-get -y install golang-go
sudo mkdir /certs
sudo mkdir /tools
cd /tools
git clone https://github.com/jsha/minica.git
cd /tools/minica
go env -w GO111MODULE=auto
go build -buildvcs=false
Here a /tools
folder is created, the minica repo is cloned within, and the source is compiled.
A /certs
folder is also created as a location for generated certificates.
Supporting h2: generate root CA certificate
Once compiled, run minica
to generate a root CA certificate (minica.pem
) and key (minica-key.pem
), which can be installed on the host to avoid browsers showing that the certificate is invalid or untrusted. This shouldn’t be done in the Vagrant box provisioning script, as you’ll get new root certificates every time the box is provisioned. Instead, SSH into the box, run, and copy the files to a secure location.. or run minica
somewhere else (e.g. host machine or another machine).
./minica --domains "test.localdev.com"
minica
requires a domain argument but the need is really for the minica.pem
and minica-key.pem
files at this point, not the site certificate. Generation of the site certificate is something that should be done within the provisioning script.
Supporting h2: generate site certificate
Within the provisioning script:
SITE_DOMAIN="localdev.com"
WILDCARD_CERT="*.localdev.com"
CERT_FOLDER="_.$SITE_DOMAIN";
CA_CERT="/vagrant/localdev/secrets/minica.pem"
CA_KEY="/vagrant/localdev/secrets/minica-key.pem"
./minica --ca-cert "$CA_CERT" --ca-key "$CA_KEY" --domains "$WILDCARD_CERT"
cp -R "/tools/minica/$CERT_FOLDER" "/certs/$CERT_FOLDER"
There’s a 2 things to note here:
- To avoid having to generate new certificates for different subdomains, a wildcard cert is generated (
*.localdev.com
) - An existing root CA certificate, and key, is expected
Supporting h2: use site certificate
Update the VirtualHost
definition to reference the site certificate and key:
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
Protocols h2 h2c http/1.1
SSLEngine on
SSLCertificateFile /certs/_.localdev.com/cert.pem
SSLCertificateKeyFile /certs/_.localdev.com/key.pem
...
Trusting minica certs on the host
Finally, to avoid the browser showing a warning that the site’s certificate is invalid or insecure, add the root certificate as a trusted root CA authority on the host machine. Martin Widmann has some nice documentation on how to do this for different operating systems.
Verify h2 protocol in the browser
With the site up, Chrome and Firefox dev tools should now show that traffic is being served via the h2 protocol.
The Protocol
column isn’t visible by default, so you may have to enable it (right click on visible header → select Protocol
).