Increasingly technologies that power the web platform require HTTPS to function (HTTP/2, Service Workers etc.), but there is still a surprising amount of friction involved in trying to setup a local HTTPS enabled development environment.
In this post, our goal is to configure a Mac to provide HTTPS with a trusted certificate using nginx as the web server.
We have the following goals:
https://example.sites.localdomain
will be served by: ~/sites/example.sites.localdomain/index.html
Per RFC 2606 you should choose a top level domain (TLD) that is reserved (such as .test
), to avoid conflicts.
However your mileage may vary. Many corporate VPNs require all DNS requests to travel through the VPN preventing/impeding resolution of such a TLD. In my case .localdomain
worked for me, whereas .test
did not, so I chose to use .localdomain
in this guide.
We will use Homebrew to install the software we need to make this all work:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
We need dnsmasq to provide wildcard domain name resolution.
brew install dnsmasq
We will configure DNSMasq to use our own custom configuration folder:
sudo echo 'conf-dir=/usr/local/etc/dnsmasq.d,*.conf' >> $(brew --prefix)/etc/dnsmasq.conf
Create our custom configuration
sudo mkdir -p $(brew --prefix)/etc/dnsmasq.d
sudo echo 'address=/.localdomain/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.d/localdomain.conf
Configure dnsmasq to run at startup:
sudo brew services restart dnsmasq
Next we need to configure MacOS to have dnsmasq handle
DNS lookups for the .localdomain
top level domain (TLD):
sudo mkdir -p /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localdomain'
We will use mkcert to generate a wildcard certificate that we configure our Mac to trust.
brew install mkcert
brew install nss # if you use Firefox
mkcert -install
sudo
password, so mkcert can do the necessary configurationsudo mkdir $(brew --prefix)/var/www/sites
sudo chgrp 755 $(brew --prefix)/var/www/sites
sudo chown `whoami`:staff $(brew --prefix)/var/www/sites
cd ~
ln -s $(brew --prefix)/var/www/sites sites
cd ~/sites
mkcert "*.sites.localdomain" sites.localdomain
sites.localdomain
domain and any sub domain of sites.localdomain
Now we want to install nginx and configure it to serve the wildcard domains under https://sites.localdomain
:
brew install nginx
To configure Nginx, we will need to interactively edit the nginx configuration file.
To edit the file using nano:
sudo nano $(brew --prefix)/etc/nginx/nginx.conf
You should see a file like the following:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 8080;
server_name localhost;
...
}
...
include servers/*;
}
Add the following between the second last and last lines (i.e. before the closing curly brace):
include /usr/local/var/www/sites/nginx.conf
We want to configure nginx to redirect all http://
URLs to their https://
equivalent:
Edit the server
block shown above to be:
{
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
# Leave commented out blocks as is
...
}
80
, mark as the default_server
server_name
to _
to catch all server namesreturn 303 https://$host$request_uri;
to force redirect to HTTPSNext create the above mentioned nginx configuration file:
nano ~/sites/nginx.conf
Add the following contents to this file and save it:
server {
listen 443 ssl default_server;
ssl_certificate /usr/local/var/www/sites/_wildcard.sites.localdomain+1.pem;
ssl_certificate_key /usr/local/var/www/sites/_wildcard.sites.localdomain+1-key.pem;
server_name _;
root /usr/local/var/www/sites/$host;
location / {
# Allow index.json or index.html for /
index index.json index.html;
# Redirect attempts to access .html
if ($request_uri ~ ^/(.*)\.html$) {
return 302 /$1$args;
}
# try adding .html or / to locate the resource
try_files $uri $uri.html $uri/ =404;
}
# Do not any file starting with . except .well-known
location ~ /\.(?!well-known).* {
deny all;
access_log off;
return 404;
}
# Do not serve any file starting with _
location ~ /_.* {
deny all;
access_log off;
return 404;
}
# Allow custom configuration of each site
include /usr/local/var/www/sites/$host/_nginx/*.conf;
}
This configures a bunch of handy things:
~/sites/$host
where $host
is the full hostname of the serverindex.json
or index.html
to be served as the resource for URLs ending in /
.html
to be served without the file extension.
(e.g. .gitignore
, .git/
) or _
(.e.g. _nginx/
)~/sites/$host/_nginx/*.conf
It’s very easy to make a mistake while editing Nginx configuration files, use the following command to validate your configuration:
nginx -t
Each time you make a configuration change you must restart nginx as follows:
sudo brew services restart nginx
Let’s stub out some basic HTML to make sure our configuration is working properly:
cd ~/sites
mkdir -p sites.localdomain
mkdir -p example.sites.localdomain
echo "<ul><li><a href='https://example.sites.localdomain'>Example Site</a></li></ul>" > sites.localdomain/index.html
echo "<h1>Welcome to the Example Site</h1>" > example.sites.localdomain/index.html
Now try navigating to the following URL in your browser:
https://sites.localdomain
You should see something like the following:
Note the padlock icon, indicating a correctly validated certificate.
If you click the Example Site link you should see something like the following:
Again note the padlock icon (Try clicking on the padlock to review the certificate), which indicates HTTPS is being used.
There is still a bit too much boiler plate involved in setting up HTTPS on a local machine, but once you do get this wildcarding approach configured it does become very straigtforward to create a new HTTPS enabled site, it becomes as simple as:
mkdir ~/sites/something.sites.localdomain