How to Setup 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 wears many hats; from coding and design, to marketing and partnerships. Before starting Delicious Brains, Brad was a busy freelance web developer, specializing in front-end development.

  • clifgriffin

    This is one of the advantages of using Laravel Valet. They automate this with the “valet secure” and “valet unsecure” commands.

    Only caveat is that if you’re going to use it with WPMDBP, you have to follow the instructions for compiling PHP to use curl/OpenSSH instead of its default security provider.

    • I came here to post what you posted.

    • Yep, good point. Evan actually pointed this out when reviewing my post. I haven’t personally used Valet yet myself, so I didn’t want to comment on it, but it definitely sounds cool. Valet may just be a topic for another article. 🙂

      • A lot of support for Valet in this thread, which I haven’t tried yet. Your article on Valet ideas would be great… or a round-up of the top WP local dev environments: Valet, VVV, Trellis, Flywheel’s Local (just discovered on this thread, looks awesome), MAMP Pro, Desktop Server, etc. I mainly use Trellis and VVV, but gosh darn I spend a lot of time ironing out bugs.

  • Adam Lenz

    I recently had to start enabling the flag at chrome://flags/#allow-insecure-localhost because the keychain alone was not enough for chrome.

  • I’ve been doing pretty much this same setup but without your “provide a configuration file to OpenSSL and define the SAN in that configuration file” step and everything has worked fine. I’ll add that step to the mix and see if there’s a change. Great post as usual…

  • Quasel

    one of the reasons I use local form flywheel former pressmatic for WordPress Local – on click ssl setup, one click xdebug setup for phpstorm, docker based, mail catcher, and more 😀

  • It’s just so much easier than messing with VMs, especially for small projects where we’re not worried about using services that may behave differently on mac than linux. What Valet does is actually really not complicated. It’s pretty basic software. But it solves all of these little issues which always give us trouble.

    • clifgriffin

      Yup. It’s a really elegant package.

      • It’s funny because I do very little WP dev these days — mostly Laravel and Django — except supporting old projects. I had to get a dev environment set up for one of these WP sites the other day. I just imported the live DB to my machine with matching username/password, downloaded the live WP site into my parked Valet directory, setup my hosts file to spoof what’s in the wp-config.php on the live site, and I was off and running. So easy. No config. Valet just did the work for me. To secure it — valet secure. DONE.

  • Jamie Oastler

    If you have a single Vagrant / VM with many dev domains on it, you can use DNS.2, DNS.3, DNS.4, etc in the SAN section to have a single cert apply. This solution also applies when doing a CSR for an actual certificate that can reduce your time / cost on number of certs to manage from a common server.

    • Good tip Jamie, though you would still have to generate a new cert any time you needed to add a new domain to the cert.

  • Mike Ð Hale

    All AWS: Beanstalk => .dev(SSL Let’s Encrypt w/ strict VPN policies) => .staging(SSL Let’s Encrypt w/ strict VPN polices) => .production (SSL Thawte). *non-TLD for dev and and staging EC2’s + free AWS SSL CDN using a custom c-name from Cloudfront’s SSL manager on all environments for 100% replicated testing environments since sometimes certain files need whitelisting when using a CDN, and smaller instances for both dev/staging yet same server setup with RDS.

    for .dev/.stg using let’s encrypt just whitelist LE IP range and added -e cron 0 1,13 * * * /home/user_ID/certbot-auto renew (no worries to keep renewing).

    switching from local .dev to cloud .dev (AWS) made life easier by setting policies for certain developers and launching a new .dev environment from multiple custom AMI’s made (replica of current site, default wordpress setup, etc).

    Of course, using WP Migrate Pro to sync RDS instances and Beanstalk git for the other files (their built in SSH deployments are great)

  • Brian Larson

    Great article! They always are. I think I got this working through the MAMP PRO UI with some other tweaks but gave up when it messed with doing development with Browser Sync. Any insight on this or is Browser Sync working fine with this method? Thanks!

  • Paul Vincent

    Does that include an intermediate certificate?

  • Jeff Richards

    I use Desktop server. Does this solution work with that?

  • Shawn Beelman

    Anyone know how to make the certificate for Codekit’s local server permanent?

  • jmcalester

    Thanks Brad! Finally https:// on my local. I used the MAMP Pro tips. Cheers!

  • If you have a few servers you need to do this with, you can just create yourself a CA (Certifying Authority) certificate and load that instead. Then your self-signed certs, signed by your CA cert, will all be accepted without you needing to load each one.

    • Ooo, thanks for that next level stuff Ross! Might just have to write a followup. 🙂

      • Yeah, would love to see this detailed. Thanks as always!

  • Nick Kulavic

    Flywheel makes this simple…

    I’m in same setup as you, all in one setup.

  • clifgriffin and Erik, your comments here convinced me to install Valet – so far so good!

  • Alex

    You’ve just ended a few hours of me banging my head against the wall. Thanks!

  • Also, just as a slight addition that can make the process easier – I add the .crt and .key and now .conf files into an easily accessed directory for each project within my /Sites/ directory. So I have ~/Sites/project/ssl for the ssl related .crt and .key and now .conf files and ~/Sites/project/web for the various web related files and directories. Then you can easily access the .crt file to double click to open in Keychain rather than dragging the certificate from Chrome’s Dev Tools as you have instructed in the “Now, drag the little certificate icon into a folder in the Finder app.” step. Double clicking the .crt to open in Keychain Access does the same thing and is simpler than dragging out of Chrome Dev Tools to create the .cer file.

  • Great article, @bradt66:disqus! I wish this article existed when I discovered the SubjectAltName change in Chrome 58 a month or two ago. I struggled with all of these points—subjectAltName, cert config, Keychain, and all. However, allow me to share a very useful trick I learned: how to add a trusted certificate to Keychain—in a single terminal command:


    sudo security
    add-trusted-cert
    -d
    -k /Library/Keychains/System.keychain
    /path/to/foo.localhost.crt

    ^ Of course, you’ll need to change the path to the certificate you wish to add. What I actually ended up doing was throwing this into a Bash script that reads a list of hostnames from a file, and for each hostname, generate the cert with openssl and add it to Keychain with the above command. As

    Oh, and here’s the command to remove an existing certificate, too (which I’ve also scripted so regenerating certificates is easy and clean):


    sudo security
    delete-certificate
    -c foo.localhost
    /Library/Keychains/System.keychain

    Okay, that’s all I wanted to share. At any rate, great article!
    Caleb

  • Thanks for the help. This worked like a charm for my dev url’s, but sadly it failed for localhost. Anyone have the same issue?

    • Never mind, user error! I forgot to update the DNS line inside the .conf file.