SSL Certificate Issue with DDEV on Docker Desktop (WSL2) behind Zscaler Proxy

Environment:

  • Windows 11 with WSL2 (Ubuntu)
  • Docker Desktop with WSL2 integration enabled
  • DDEV v1.24.8
  • Drupal 10 project running inside DDEV containers

Issue:
When running ddev composer install behind a Zscaler proxy, I get the following error:

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details: curl - SSL CA Certificates

What I tried:

  • Followed the official Docker guide for Zscaler: Using Docker with Zscaler | Docker Docs
  • Added Zscaler root CA certificate inside the DDEV container using custom Dockerfile and update-ca-certificates
  • Verified certificate is present in /usr/local/share/ca-certificates and ran update-ca-certificates
  • Restarted DDEV and Docker Desktop multiple times

Despite these steps, the container still fails SSL verification. It seems the CA trust is not being applied correctly inside the container.

Question:

Has anyone successfully resolved SSL certificate issues with Zscaler when using DDEV on Docker Desktop with WSL2 for Drupal 10 projects? Any best practices or configuration tips would be appreciated.

These are the questions I would normally ask:

  • Did you confirm that the rejected SSL cert was from zscaler?
  • Are you sure you built the new image and not used something from cache?
  • If you know you built the image, have you checked that the CA was actually added to the CA bundle file?
  • Are you sure the rejected SSL is still zscaler?

But since you run `ddev command not Docker directly, don1t know what gives you the error. If the error is coming from an already running container, please, answer my questions above. If you know what URL is accessed when the error is shown, you can try testing the SSL like menitoned here:

Just change the domain name and port if needed.

If it is a ddev issue, you could try a ddev forum

Thank you for your questions. Here are my answers and additional test results:

  1. Did you confirm that the rejected SSL cert was from Zscaler?
    Yes. I ran:

So Zscaler is intercepting SSL traffic, but the container does not trust Zscaler CA.

  1. Are you sure you built the new image and not used something from cache?

    Yes. I ran:

    ddev restart --stop --remove-data --force-build
    This forces a fresh build of the web container.

  2. If you know you built the image, have you checked that the CA was actually added to the CA bundle file?

    Yes, I checked inside the container:

    grep -i “Zscaler” /etc/ssl/certs/ca-certificates.crt

    Result: Giving nothing that means “Zscaler not found in CA bundle” i think

So the CA was not successfully added to the system bundle may be. This means the Zscaler root certificate was never added to the system CA bundle inside the container, so PHP, Composer, and cURL cannot validate Zscaler-intercepted SSL connections.

  1. Are you sure the rejected SSL is still Zscaler?

    Yes. All HTTPS requests (packagist.org, docker.io) fail with curl error 60, and openssl confirms Zscaler in the chain.

Additional tests:

  • PHP/OpenSSL CA path:

    openssl.cafile= (empty)
    openssl.capath= (empty)

    So PHP and Composer rely on the system CA bundle.

  • Composer diagnose:

    All HTTPS checks fail with curl error 60.

Behavior comparison:

  • On a non-corporate laptop (no Zscaler), everything works fine.
  • If I disable Zscaler Client Connector on my corporate laptop, the issue disappears.
  • The problem only occurs when Zscaler is active, which is mandatory for corporate network access.

Root cause:

This is not a DDEV issue—DDEV only orchestrates Docker. The problem is CA trust inside the container I think. May be the container does not trust Zscaler CA because it is missing from /etc/ssl/certs/ca-certificates.crt. I think PHP and Composer use this system CA bundle, so all HTTPS requests fail. Manual changes inside the container (like adding the cert) are lost after ddev restart because containers are rebuilt. The CA must be added during the image build process or via persistent configuration.

Could you please suggest the best practices for managing CA trust in Docker-based environments behind Zscaler? I have tried multiple approaches (manual certificate addition, update-ca-certificates, custom configuration), but none have worked consistently. Any recommended steps or official guidance would be greatly appreciate

You are working with an abstraction that controls the docker engine. It is a black box for us.

If you take DDEV out of the equation, you would simply create a Dockerfile that adds the Zscaler ca certificate to the image, and then updates the -ca-certificates. Then build an image from this Dockerfile, and create the container from this image.

Thus said, please share how you ensure that DDEV does the same? Please provide the exact commands and code snippets required to understand what you did exactly.

The CA bundle file contains certificates in base64 encoded format. You cannot simply grep for a name of a certificate authority. But if you know the base64 encoded version of the CA, you can grep for part of that.

Or you can use this script with grep at the end:

awk 'BEGIN { c = "openssl x509 -noout -subject" } /BEGIN CERTIFICATE/ { close(c) } { print | c }' ca-certificates.crt | grep -i Zscaler

But as @meyay and I also pointed out, it would be easier if we could make this issue a pure Docker issue trying to solve one problem at a time.

In addition to the already recommended, you can also just run a container from the same base image that you use with ddev and manually test the commands incluing curl and updating CA certs. I would not rely on composer or PHP first, only when I see everything works without them. That is how you can pinpoint the root cause of the issue.

1 Like

@rimelek @meyay Thanks for quick response, I have resolved the Zscaler-related SSL trust errors in DDEV by baking corporate CA certificates into both the web and db images during build.

Workaround Steps

  • Added custom Dockerfiles:
    • .ddev/web-build/Dockerfile
    • .ddev/db-build/Dockerfile
  • Installed ca-certificates, copied Zscaler and corporate CA PEM files into /usr/local/share/ca-certificates/, and ran: update-ca-certificates --fresh
  • Verified symlinks under /etc/ssl/certs (e.g., zscaler.crt, corporate-chain.crt).
  • Set environment variables for tools that require explicit CA paths:

ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
ENV COMPOSER_CAFILE=/etc/ssl/certs/ca-certificates.crt
ENV GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt

Results

  • Web container: curl -I https://packagist.org returns HTTP/2 200; composer diagnose reports HTTPS OK.

  • DB container: curl -I https://downloads.mariadb.org succeeds (HTTP 302 Found). Previous curl (60) errors during mariadb_repo_setup are gone.

  • npm/yarn fixed by:

      npm config set cafile /etc/ssl/certs/ca-certificates.crt
      yarn config set cafile /etc/ssl/certs/ca-certificates.crt
    

Additional Notes

  • Manual edits inside running containers are lost on ddev restart; CA trust must be established during image build.
  • If Zscaler requires intermediate certs, concatenate them under the root CA in the same PEM file.
  • Composer pubkey and version warnings were unrelated to SSL and resolved via:

composer self-update --update-keys
composer self-update

Conclusion

Bake CA trust into every image that performs HTTPS (web + db), regenerate the system CA bundle during build, and explicitly set CA-related environment variables for curl, composer, git, and Node. With this approach, curl, composer, npm, and other tools work seamlessly behind Zscaler.

Please let me know if there are any best practice behind Zscaler.