Home Blog Local SSL with WP-ENV

Getting Local SSL Working with WP-ENV + Docker on WSL2

WP-Env SSL blog post featured image

For local WordPress development I use Docker. Actually, to be honest, for most development projects I use Docker. Because Docker is awesome. And for WordPress specifically, I like using WP-ENV. It makes configuring an environment quick and easy, whether I’m working on themes, plugins, or both. But one thing it does not have out of the box is SSL.

I wanted local SSL for a specific project so I could test secure cookies, redirects, and third-party integrations in a realistic environment. And honestly, I just wanted to see if I could figure it out.

Spoiler Alert: I did figure it out.

How, you ask? Let’s get into it.

My Local Setup

Just so you understand my local setup, here’s what I got going on.

  • @wordpress/env: 4.x series (I know… waaaay out of date)
  • I have a bash script that attaches PhpMyAdmin and Mailhog containers to WP-ENV’s docker network
  • Using WSL2 (Debian) with Docker installed

I know at some point I need to upgrade WP-ENV, but I’m going to be migrating from WSL2/PC to a full Linux set up anyway, so might as well deal with that then. For right now I just wanted to try to get this done. However, this should work regardless of your version of WP-ENV.

If You Are on Newer WP-ENV

I asked Chat GPT what users with a newer version of WP-ENV might need to consider, and this is what it said:

  1. Newer versions still default to HTTP for the local WordPress URL (http://localhost:8888), so SSL is not turnkey out of the box.
  2. Newer versions support phpMyAdmin configuration directly in .wp-env.json (for example "phpmyadmin": true and "phpmyadminPort").
  3. Some CLI ergonomics changed over time (for example, the newer status command and removal of install-path in 11.x), so exact commands may differ from older guides.

So this solution should still be useful across versions, but the implementation details may be simpler on newer wp-env.

So that’s what good ole ChatGPT has to say about it. Take it for what it’s worth!

Repository Setup

Here are the important configurations in my repo that you need to know about.

In .wp-env.json:

  • Theme mapping: wp-content/themes/{theme name} -> .
  • Plugin mapping: wp-content/plugins/ -> ./plugins
  • Local site constants:
    • WP_HOME: https://yoursite.dev
    • WP_SITEURL: https://yoursite.dev

In this repo, I run helper containers for:

  • phpMyAdmin on http://localhost:8080
  • MailHog on http://localhost:8025

So How Did I Do it?

Alright Kyle, get to the point already. How did I add SSL to my project? Well, it’s actually quite simple. I added an nginx reverse proxy container in front of the wp-env WordPress container.

This is the flow:

  1. Browser hits https://yoursite.dev
  2. nginx handles TLS on :443
  3. nginx proxies to http://wordpress:80 on the Docker network
  4. WordPress responds through nginx with HTTPS-aware forwarded headers
    • X-Forwarded-Proto https and HTTPS on headers so WordPress treats requests as secure

I have an nginx config in my repo at .docker/nginx/default.conf. I also have a .docker/certs directory where I use mkcert to install the locally trusted certs.

Docker Orchestration in This Repo

As I mentioned earlier, I have custom scripts that add PhpMyAdmin and Mailhog to the Docker setup. So it made sense to wire this into these existing scripts:

  • Start: docker-up.sh
  • Stop: docker-down.sh

docker-up.sh does this

  1. Validates nginx config + cert files exist
  2. Runs wp-env start
  3. Forces WP_HOME and WP_SITEURL constants to https://yoursite.dev
  4. Detects the active wp-env Docker network
  5. Starts and attaches “sidecars” PhpMyAdmin and Mailhog containers
  6. Starts Nginx SSL proxy on ports 80 and 443

docker-down.sh does this

  • Removes sidecars + SSL proxy
  • Runs wp-env stop

Certificate Setup with mkcert

Here’s how I generated the certs. Be sure to swap out your local dev domain name.

mkcert -install
mkcert -cert-file .docker/certs/yoursite.dev.pem \
  -key-file .docker/certs/yoursite.dev-key.pem \
  yoursite.dev localhost 127.0.0.1 ::1

I also needed to add this host entry:

127.0.0.1 yoursite.dev

The WSL2 Gotcha

After setting everything up I still encountered a “Your connection is not private” error when I visited the site in Brave browser. Why would that be? Well… it’s because I’m running WSL2 on Windows.

The WSL Linux trust store and Windows trust store are separate. The cert was trusted in Linux, but not in Windows where the browser runs.

Fix for WSL2 + Windows browser

The fix for this is to import the WSL mkcert root CA into the Windows trust store:

ROOT_CA_WIN_PATH=$(wslpath -w "$(mkcert -CAROOT)/rootCA.pem")
powershell.exe -NoProfile -Command "Import-Certificate -FilePath '$ROOT_CA_WIN_PATH' -CertStoreLocation Cert:\CurrentUser\Root | Out-Null"

Then after fully closing/reopening the browser, everything was working.

Another Real-World Issue: Wrong :8888 Redirects

At one point in the process of getting all of this setup, WordPress started redirecting to URLs like:

  • https://yoursite.dev:8888

The issue seemed to be that WP ENV wasn’t honoring the values I had set in the .wp-env.json file. So for me, the fix was to explicitly set WP_HOME and WP_SITEURL constants after wp-env start in docker-up.sh script, so generated URLs always stay on https://yoursite.dev. This may have been an issue with my version of WP-ENV… I’m not sure.

Final Workflow

So now to get my repo running locally with SSL, I simply run:

bash docker-up.sh

Open:

  • https://yoursite.dev

When done:

bash docker-down.sh

That’s it! Easy! If you’re interested in seeing the code in more detail you can check out this repo here. And if you want to chat, hit me up on X!