SSL / TLS issues
apm install and apm audit reach out to GitHub, GHES, GitLab, Azure DevOps, and package archives over HTTPS. When the system can’t verify the server certificate, the operation fails. This page maps the failure modes to fixes.
Related: environment variables, install failures, security model, authentication.
Symptoms
Section titled “Symptoms”Typical errors APM surfaces or passes through from the underlying HTTP/git stack:
[!] TLS verification failed -- APM uses the system trust store by default. If you're behind a corporate proxy or firewall, make sure your organisation's CA is installed in the OS trust store, or set APM_EXTRA_CA_BUNDLE to a readable PEM bundle and retry.SSLError: HTTPSConnectionPool(host='api.github.com', port=443): Max retries exceeded ... [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificatefatal: unable to access 'https://github.example.com/...': SSL certificate problem: self-signed certificate in certificate chainfatal: unable to access '...': server certificate verification failed. CAfile: none CRLfile: noneAll of these mean the same thing: the TLS chain presented by the server can’t be validated against the trust store APM is using.
First diagnostic
Section titled “First diagnostic”Decide which of the three categories you are in before changing anything:
[*] Corporate TLS-intercepting proxy (Zscaler, Netskope, Palo Alto, Cisco Umbrella, Blue Coat). The server cert is re-signed by an internal CA. Affects every HTTPS host. Fix: trust the corporate CA.
[*] Self-hosted server with internal CA (GHES, GitLab self-managed, internal artifact host). Only that one host fails; public hosts like api.github.com work fine. Fix: trust the internal CA, often per-host.
[*] Genuine certificate problem (expired, wrong hostname, broken chain). Reproduce with curl -v https://<host> from the same shell. If curl also fails, the problem is upstream of APM.
Re-run the failing command with --verbose to see the underlying exception and the host that triggered it:
apm install --verboseDefault behaviour: the OS trust store
Section titled “Default behaviour: the OS trust store”Fastest fix: install your corporate CA in the OS trust store and retry. APM picks it up automatically on its Python paths.
APM verifies HTTPS against the operating-system trust store by default (via truststore), the same source git and curl use. This covers in-process commands such as apm install and the standalone frozen binary, with bundled certifi as a fallback.
Scope caveat: APM_EXTRA_CA_BUNDLE applies to APM’s own package-management HTTPS, including installation, registry access, and downloads. It does not derive trust settings for experimental apm run children. Node/Copilot and Rust/Codex retain their runtime-owned trust configuration; configure Node’s native NODE_EXTRA_CA_CERTS separately when needed.
For the Python-based llm child runtime, apm runtime setup llm installs truststore in its virtual environment and adds a self-contained OS-trust bootstrap. Corporate CAs installed in Keychain on macOS, through update-ca-certificates/update-ca-trust on Linux, or in the Windows Trusted Root store then work without APM-specific configuration.
You only need the settings below when the CA is not in the OS store, or you intentionally want to pin a replacement bundle:
APM_EXTRA_CA_BUNDLEadds a readable PEM bundle to APM’s active defaults: OS roots whiletruststoreis active, or bundledcertififor the Requests fallback. This is the recommended per-shell corporate CA setting.REQUESTS_CA_BUNDLEorCURL_CA_BUNDLEmakes APM’s Python HTTP layer verify against that bundle instead of the OS store. (SSL_CERT_FILEconfigures the stdlibssllayer but is not read byrequests, so on its own it does not override the HTTP path – useREQUESTS_CA_BUNDLEfor that.)APM_DISABLE_TRUSTSTORE=1disables APM’s OS/additive trust. It does not unsetREQUESTS_CA_BUNDLEorCURL_CA_BUNDLE; an explicit replacement still controls Requests.
The exact order is REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, APM_DISABLE_TRUSTSTORE, APM_EXTRA_CA_BUNDLE, the OS trust store, then bundled certifi as the final Requests fallback. Higher-precedence controls suppress additive-bundle validation. Debug trust-source output names when the extra bundle is ignored by a higher-precedence setting. Leaving the extra bundle unset or blank preserves existing behavior.
Runtime coverage
Section titled “Runtime coverage”| Path | Behaviour when APM_EXTRA_CA_BUNDLE is selected |
|---|---|
| APM package-management Requests HTTPS | Retains native OS roots and adds the selected PEM certificates; falls back to certifi plus the extra CA if OS injection is unavailable. |
| In-process stdlib metadata HTTPS | Receives OS-plus-extra trust while truststore is active; on injection failure its HTTPS context retains the stdlib defaults plus the extra certificates. |
| Python/Requests execution child | Unchanged; no additive settings are derived. |
Managed Python llm child |
Unchanged; the existing setup-time bootstrap still provides OS trust when available. |
| Node/Copilot child | Unchanged; set NODE_EXTRA_CA_CERTS using the runtime’s own trust settings. |
| Git | Unchanged; configure GIT_SSL_CAINFO or Git’s native trust settings separately. |
| Rust/Codex | Unchanged; configure the runtime’s own trust settings. |
Known limitations
Section titled “Known limitations”- Git uses its own trust configuration;
APM_EXTRA_CA_BUNDLEdoes not changegit clone,git fetch, orgit ls-remote. ConfigureGIT_SSL_CAINFOseparately when Git needs the same CA. - Rust-based Codex uses its own runtime trust configuration. APM does not translate
APM_EXTRA_CA_BUNDLEinto a Rust/OpenSSL setting. - If truststore injection is unavailable, Requests-based HTTPS retains
certifiroots plus the extra certificates. The stdlib HTTPS context used by metadata requests retains its own default roots plus the extra certificates. Raw stdlib SSL contexts keep their existing settings. - The
llmchild runtime’s OS-trust bootstrap needs the runtime venv’s interpreter to be Python 3.10+ (thetruststorelibrary requires 3.10). On systems whereapm runtime setup llmbuilds the venv from a stock Python 3.9 (for example Apple’s/usr/bin/python3),truststorecannot install, so CAs present only in the OS store are unavailable to that child. That child keeps its existing certificate defaults; the additive APM setting does not apply to it. Use a Python 3.10+python3on yourPATHbefore running setup when the child must use native OS trust. - The initial
pip installrun duringapm runtime setup llmuses pip’s own certificate resolution, not APM’s OS-trust path. Behind a MITM proxy,pipmay fail to fetchllm/truststorebefore the bootstrap is even in place. ExportPIP_CERT=/path/to/org-ca-bundle.pem(or runpip config set global.cert /path/to/org-ca-bundle.pem) before running setup so pip trusts your proxy CA.
Configure trust
Section titled “Configure trust”APM’s primary Python HTTP paths use requests, a small number of metadata paths use the stdlib urllib stack, and repository operations shell out to git. Their fallback trust settings differ as described above. Set them at the shell or in your profile (~/.zshrc, ~/.bashrc, or the Windows user environment).
Python HTTP layer
Section titled “Python HTTP layer”export APM_EXTRA_CA_BUNDLE=/path/to/corporate-ca.pemThis retains native OS roots and adds the selected PEM certificates. If APM cannot inject OS trust, Requests-based HTTPS retains the additive certificates over its certifi fallback. APM validates one in-memory copy of the bundle before using it: the file must be regular, readable, non-empty, no larger than 8 MiB, certificate-only ASCII PEM, and contain at least one certificate. Private-key blocks are rejected. An invalid selected bundle fails closed before the command runs, rather than silently ignoring the requested trust or disabling verification. Named pipes are rejected without waiting for a writer. Set trust controls in the environment that launches APM.
Use a replacement bundle only when you intend to pin the entire Requests trust set:
export REQUESTS_CA_BUNDLE=/path/to/ca-bundle.pemREQUESTS_CA_BUNDLE wins for requests. SSL_CERT_FILE / SSL_CERT_DIR cover parts of the stdlib TLS stack, but on their own they are not reliable overrides for the requests HTTP path APM uses.
Node children
Section titled “Node children”Configure Node independently; APM does not translate APM_EXTRA_CA_BUNDLE into a Node setting:
export NODE_EXTRA_CA_CERTS=/path/to/node-ca-bundle.pemGit operations
Section titled “Git operations”export GIT_SSL_CAINFO=/path/to/ca-bundle.pemFor one host only, prefer per-host git config so you don’t widen trust globally:
git config --global http.https://github.example.com/.sslCAInfo /path/to/internal-ca.pemThe trailing slash matters - it scopes the setting to that origin.
Windows (PowerShell)
Section titled “Windows (PowerShell)”$env:APM_EXTRA_CA_BUNDLE = "C:\certs\corporate-ca.pem"$env:GIT_SSL_CAINFO = "C:\certs\corporate-ca.pem"
# Persist for the current user:[Environment]::SetEnvironmentVariable("APM_EXTRA_CA_BUNDLE", "C:\certs\corporate-ca.pem", "User")Where do I get the CA file?
Section titled “Where do I get the CA file?”Your IT or platform team owns it. Ask for the PEM bundle for the proxy or internal PKI. Do not export it yourself from a browser unless that is the documented procedure - you may capture an intermediate, not the root.
GHES and GitLab self-managed
Section titled “GHES and GitLab self-managed”Trust alone is not enough for self-hosted forges. APM also needs to know which host to talk to.
GHES:
export GITHUB_HOST=github.example.comexport GITHUB_APM_PAT=<token>export GIT_SSL_CAINFO=/path/to/internal-ca.pemGitLab self-managed:
export GITLAB_HOST=gitlab.example.comexport APM_GITLAB_HOSTS=gitlab.example.com,gitlab-eu.example.comexport GITLAB_APM_PAT=<token>export GIT_SSL_CAINFO=/path/to/internal-ca.pemSee environment variables for the full list and authentication for token scopes.
Proxies
Section titled “Proxies”APM does not implement its own proxy logic. It honours the standard variables, which requests and git both read:
export HTTPS_PROXY=http://proxy.example.com:8080export HTTP_PROXY=http://proxy.example.com:8080export NO_PROXY=localhost,127.0.0.1,.internal.example.comIf the proxy performs TLS interception, you also need the proxy’s signing CA in the trust store - see Configure trust. Importing the CA into the OS trust store (Keychain on macOS, update-ca-certificates on Debian/Ubuntu, update-ca-trust on RHEL, the Trusted Root store on Windows) is the most durable fix; consult your OS documentation rather than copying steps from here.
Verify the fix
Section titled “Verify the fix”# APM Python HTTPS pathAPM_LOG_LEVEL=DEBUG apm install
# Git sideGIT_CURL_VERBOSE=1 git ls-remote https://github.example.com/org/repo.git 2>&1 | grep -i 'ssl\|cert'The debug output identifies whether APM selected the OS trust store, additive bundle, replacement bundle, or certifi fallback. That line plus a clean install confirms APM’s in-process Python path; a successful ls-remote confirms Git trust separately.
Development-only escape hatches
Section titled “Development-only escape hatches”If you are isolated on a laptop, debugging a local server with a self-signed cert, and you accept the risk:
export GIT_SSL_NO_VERIFY=true # git onlyexport PYTHONHTTPSVERIFY=0 # Python stdlib only; requests ignores thisWhat you lose: any guarantee that the host you reached is the host you intended to reach. Tokens you send may be captured. Packages you download may be tampered with - APM’s built-in security scanning still runs on the bytes received, but it cannot detect substitution upstream of itself.
Unset both as soon as you are done:
unset GIT_SSL_NO_VERIFY PYTHONHTTPSVERIFYStill failing?
Section titled “Still failing?”[>] Re-run with --verbose and capture the full exception chain.
[>] Check curl -v https://<host> from the same shell - if it fails, the problem is the system trust store, not APM.
[>] Confirm APM_EXTRA_CA_BUNDLE points at a readable, non-empty regular file no larger than 8 MiB and contains certificate-only ASCII PEM. APM rejects malformed bundles and private-key blocks rather than silently ignoring them. Unset it to return to normal OS trust, or replace it with the correct CA file.
[>] Confirm REQUESTS_CA_BUNDLE and GIT_SSL_CAINFO point at a readable PEM file (openssl x509 -in $REQUESTS_CA_BUNDLE -noout -subject should print a subject line). Note REQUESTS_CA_BUNDLE replaces the OS store rather than augmenting it (like git’s http.sslCAInfo and curl --cacert), so a bundle missing your proxy root will still fail even though the OS store has it.
[>] If git/curl succeed but apm does not, check the precedence settings. APM_DISABLE_TRUSTSTORE bypasses OS and additive trust; a stale REQUESTS_CA_BUNDLE (or CURL_CA_BUNDLE) pins APM to a replacement bundle. Unset those variables and retry to let APM_EXTRA_CA_BUNDLE, or the OS store when it is unset, take effect.
[>] If only one host fails, see GHES and GitLab self-managed and the per-host git config recipe above.
[>] If the install proceeds past TLS but then fails, continue at install failures.