How to Set Up HTTPS Locally Without Getting Annoying Browser Privacy Errors

#

Setting up HTTPS locally can be tricky business. Even if you do manage to wrestle self-signed certificates into submission, you still end up with browser privacy errors. In this article, I’ll walk you through setting up self-signed certificates and show you a nice little trick to quiet browser privacy errors.

For at least a year now I’ve been running HTTPS in my local development environment. Last week I updated to Google Chrome 58 and something changed that nuked this setup. All of a sudden I was getting browser privacy errors again.

Screenshot of Chrome browser with privacy error

Unlike the privacy errors of the past, there was no longer any “Add Exception” option. I checked Firefox and it behaved the same. Safari still worked.

A search for ERR_CERT_COMMON_NAME_INVALID produced little results, but I eventually found the solution in the Chromium bug tracker. Turns out Chrome and Firefox have dropped support for commonName matching in certificates.

I managed to fix my setup using the suggestions in the Chromium comment (more on that later) but the whole ordeal made me realize that I hadn’t documented how to set up HTTPS locally without browser privacy errors. This article will serve as that document and I plan to update it as things change in the future.

Why HTTPS Locally?

Why not just use regular HTTP locally? Because if your production site is HTTPS-only and you’re developing locally on regular HTTP, your dev and production environments are not as similar as they could be. For example, my dev environment for this site (deliciousbrains.com) runs as an Ubuntu server in a VMware virtual machine (VM) on my Mac. The production site is an Ubuntu server running on Linode with an almost identical configuration.

You definitely want your dev environment to mirror production as closely as possible.
When it doesn’t, you invite more issues showing up in production that didn’t show up in dev. Running HTTP when your production site is HTTPS-only is definitely an unnecessary risk.

Creating a Self-Signed Certificate

Like enabling HTTPS on a production site, you first need a certificate. For a production site, you request one from a certificate authority like Let’s Encrypt, Comodo, etc. For a local dev environment, we can generate a self-signed certificate on the command line. It used to be as simple as this command:

openssl req -new -sha256 -newkey rsa:2048 -nodes \
-keyout dev.deliciousbrains.com.key -x509 -days 365 \
-out dev.deliciousbrains.com.crt

Running that command, you get asked a few questions:

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:dev.deliciousbrains.com
Email Address []:

Most of these questions weren’t important to answer for a dev environment certificate. The answers would show up when looking at the certificate information, but it didn’t have any impact on whether the browser deemed the site to be secure or not. In fact, the only question that really needed an answer was Common Name (CN). The answer to that question determined which domain the certificate was valid for.

But now, the CN question is also superficial. As of Chrome 58 and Firefox 48 it is ignored when matching a domain name to a certificate. This is exactly why I started getting privacy errors when I updated to Chrome 58.

RFC 2818 describes two methods to match a domain name against a certificate – using the available names within the subjectAlternativeName extension, or, in the absence of a SAN extension, falling back to the commonName. The fallback to the commonName was deprecated in RFC 2818 (published in 2000), but support still remains in a number of TLS clients, often incorrectly. chromestatus.com

Wow, deprecated since 2000. Definitely time to remove support.

So now the domain name must be defined in the Subject Alternative Name (SAN) section (i.e. extension) of the certificate:

Screenshot of Chrome browser showing expanded details of self-signed certificate

Now when creating a self-signed certificate, we need to provide a configuration file to OpenSSL and define the SAN in that configuration file. Our command becomes:

openssl req -config dev.deliciousbrains.com.conf -new -sha256 -newkey rsa:2048 \
-nodes -keyout dev.deliciousbrains.com.key -x509 -days 365 \
-out dev.deliciousbrains.com.crt

For the dev.deliciousbrains.com.conf configuration file, I just used the one from Stack Overflow linked in the Chromium comment I mentioned earlier.

The only change I made was replacing the DNS.1 = example.com line with DNS.1 = dev.deliciousbrains.com and removed the rest of the DNS lines underneath it. Here’s the full config with comments removed and formatting cleaned up:

[ req ]

default_bits        = 2048
default_keyfile     = server-key.pem
distinguished_name  = subject
req_extensions      = req_ext
x509_extensions     = x509_ext
string_mask         = utf8only

[ subject ]

countryName                 = Country Name (2 letter code)
countryName_default         = US

stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = NY

localityName                = Locality Name (eg, city)
localityName_default        = New York

organizationName            = Organization Name (eg, company)
organizationName_default    = Example, LLC

commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = Example Company

emailAddress                = Email Address
emailAddress_default        = test@example.com

[ x509_ext ]

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
subjectAltName         = @alternate_names
nsComment              = "OpenSSL Generated Certificate"

[ req_ext ]

subjectKeyIdentifier = hash

basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
subjectAltName       = @alternate_names
nsComment            = "OpenSSL Generated Certificate"

[ alternate_names ]

DNS.1       = dev.deliciousbrains.com

If you’re using MAMP, you may be tempted to generate your self-signed certificates using the MAMP UI:

Screenshot of MAMP Pro interface showing the SSL configuration tab for a local site

I tried this with MAMP 4.1.1 but unfortunately it doesn’t define a SAN and so you’ll get the ERR_CERT_COMMON_NAME_INVALID browser privacy error. Until they update MAMP to define a SAN, you’ll have to generate your certificates on the command line and add them to MAMP.

Installing the Certificate

Next you’ll need to install the certificate into Nginx, Apache, or whatever web server you’re using. I’m not going to cover that here as it really depends on your environment. In my case, because I’m using an Ubuntu server I just follow the instructions from our Hosting WordPress Yourself series. If you’re using MAMP, you select the certificate and key files using the UI as shown in the screenshot above.

Once you’ve updated your web server’s config and restarted it (don’t forget to restart it), loading the site will still give you a browser privacy error:

Screenshot of Chrome browser with security warning

You’ll notice that it’s now a different error: ERR_CERT_AUTHORITY_INVALID. The browser doesn’t trust the certificate because we self-signed it instead of getting it from a certificate authority. However, we can add the certificate to our macOS Keychain and indicate that the certificate should always be trusted.

Adding the Certificate to macOS Keychain

  1. In Chrome, open the dev site you’ve configured to use the certificate
  2. Press Cmd-Alt-I to open Developer Tools
  3. Click the Security tab
  4. Click the View certificate button

You should end up with a screen that looks like this:

Screenshot of Chrome browser viewing insecure self-signed certificate

Now, drag the little certificate icon into a folder in the Finder app.

Animated gif of dragging the certificate from Chrome into the Finder

A certificate file will be created in that folder. Double click on the file. If you have multiple keychains like I do, you might get a window like this:

Screenshot of MacOS Add Certificates dialog box for adding a new certificate to a keychain

Click “Add”. If you only have one keychain, your certificate might be added to your keychain without a prompt. Regardless if you have to accept a prompt or not, a Keychain Access window should show up. Search for your certificate:

Screenshot of MacOS Keychain Access with newly added certificate

Double click on it. A window will open with the certificate details. Expand the Trust section. Change the “When using this certificate:” select box to “Always Trust”.

Screenshot of self-signed certificate added to keychain and set to Always Trust

Close the certificate window. It will ask you to enter your password (or scan your finger), do that. Now visit your dev site again.

Screenshot of Chrome browser showing a secure connection using the new self-signed certificate

That’s gold, Jerry. Gold!

To clean up, you can delete the certificate file from the folder you dragged it into since you’ve added it to the system’s keychain.

Are you currently working with HTTP-only locally? Will you be switching to HTTPS? Let us know in the comments.

Update: Some commenters pointed out that Laravel Valet makes this easier. Evan actually pointed this out when reviewing my article and I plan to give it a try in the future. If you’ve used Laravel Valet to do this, let us know in the comments.

About the Author

Brad Touesnard

As founder of Delicious Brains Inc, Brad has worn many hats. He now spends most of his time managing the product teams and growing the business. Before starting this company, Brad was a freelance web developer, specializing in front-end development.