# composer/ca-bundle: Finding the System CA Bundle in PHP Without Hardcoding a Path

> composer/ca-bundle is a small MIT-licensed PHP library that locates the system CA bundle and falls back to a bundled Mozilla CA file. It is for PHP code that makes TLS connections and needs a certificate path that works across distributions.

**composer/ca-bundle** — Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.

- Repository: https://github.com/composer/ca-bundle
- Stars: 2,958 · Forks: 40
- Language: PHP
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/composer-ca-bundle

## The problem: TLS code that assumes a CA path

PHP's curl and stream wrappers need a certificate authority file or directory to verify a TLS peer. The path to that file differs by platform and distribution: Debian and Ubuntu keep it under /etc/ssl/certs, Red Hat systems have their own location, Windows has none of these, and some minimal containers ship no CA store at all. Code that hardcodes one path breaks everywhere else, and the failure appears as a verification error rather than an obvious missing file.

composer/ca-bundle exists to answer one question: where is the CA bundle on this machine? Its README describes it as a small utility library that finds a path to the system CA bundle and includes a fallback to the Mozilla CA bundle. It was originally written inside composer/composer and later extracted as a stand-alone library, which is a useful signal about its scope. It is not a TLS client and does not perform certificate validation itself. It resolves a path and validates that the file at that path looks usable.

## How CaBundle resolves a path and falls back to Mozilla

The public surface is a handful of static methods on Composer\CaBundle\CaBundle. getSystemCaRootBundlePath() returns the system CA bundle path, or a path to the bundled one as fallback. getBundledCaBundlePath() returns the path to the bundled CA file directly. validateCaFile($filename) validates a CA file using openssl_x509_parse, but only if it is safe to use. isOpensslParseSafe() tests whether that PHP function can be used safely in the current environment. reset() clears the static caches.

That last method hints at the design: the class caches its lookups statically, so repeated calls do not repeat filesystem probing. In a long-running process, or in tests that change the environment, reset() is the way to force a fresh resolution. The safety check around openssl_x509_parse matters because older PHP builds had problems parsing certain certificate files; the library checks before trusting that function rather than calling it blindly.

The bundled Mozilla file lives in the res/ directory at the top level of the repository, alongside src/ and tests/. Shipping a CA file inside a Composer package means the fallback is available even on a system with no trust store, which is the case the library was built for. The trade-off is that the bundled file is only as fresh as the release you installed.

## Installing composer/ca-bundle and wiring it into curl

The README gives one installation command. It pulls the package from Packagist and registers its autoloader through Composer, so no manual include is needed.

```bash
$ composer require composer/ca-bundle
```

The README states PHP 5.3.2 is required, but that using the latest version of PHP is highly recommended. The version constraint in your composer.json is what decides which release you get; the repository shows parallel 1.4.x and 1.5.x release lines, so a project pinned to the older line will not receive 1.5.x changes.

The first real use is passing the resolved path to curl. The README's example checks whether the result is a directory and sets CURLOPT_CAPATH or CURLOPT_CAINFO accordingly, because some systems expose a hashed certificate directory rather than a single file.

```php
$curl = curl_init("https://example.org/");

$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
    curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile);
} else {
    curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile);
}

$result = curl_exec($curl);
```

After this, curl verifies the peer against the resolved bundle instead of the default compiled into libcurl. The same pattern appears in the README for PHP stream contexts, where the key is either $opts['ssl']['capath'] or $opts['ssl']['cafile'], and for Guzzle, where the path is passed as GuzzleHttp\RequestOptions::VERIFY.

## Where composer/ca-bundle is the wrong tool

The library resolves a path. It does not check revocation, pin certificates, or enforce a minimum key size. If your threat model requires OCSP or CRL checking, this package does nothing for you, and neither does the curl verification it feeds into by default. You would need a layer that performs those checks.

There is also a staleness problem. The fallback is a Mozilla CA file shipped inside the package, so its contents are fixed at release time. A system whose trust store is missing or unreadable will silently fall back to that file, and the certificates it trusts are the ones Mozilla trusted when the release was cut. The README does not document a way to refresh the bundled file independently of upgrading the package.

Finally, if your application already ships its own CA file and pins it deliberately, this library adds a resolution step you do not want. Pinning is a choice; automatic discovery is the opposite of pinning. Mixing the two without deciding which one wins is where verification bugs come from.

## How it differs from hardcoding /etc/ssl/certs or letting curl decide

The obvious alternative is to pass nothing and let libcurl use the CA bundle it was compiled with. That works when the PHP build and the system agree, and it fails on the cases this library was written for: a PHP binary built against a different prefix than the running system, a container with no CA store, or a Windows host with no equivalent path. The difference is that libcurl's default is fixed at build time, while composer/ca-bundle probes the running system and can fall back to a file it carries.

The second alternative is hardcoding a path such as /etc/ssl/certs/ca-certificates.crt. This is simpler and has no dependency, and it is correct on a known image. It breaks the moment the image changes base, which is a common event. The library's approach is to probe and to keep a fallback, at the cost of a dependency and a file in the package. Neither approach performs revocation checking, so that is not a differentiator between them.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-02. Recent releases include 1.5.14 and 1.4.15, both dated 2026-08-21, and 1.5.13 dated 2026-07-18. The two maintained lines mean a security fix to the bundled CA file may land in both, but a project on 1.4.x will not receive API changes made only in 1.5.x.

The practical upgrade cost is low because the API is a set of static methods, and the README documents no configuration file or service to migrate. The real cost is the bundled Mozilla file: keeping it current means upgrading the package, so a project that pins an old version keeps an old trust list. The repository includes phpstan.neon.dist and phpunit.xml.dist, which indicates static analysis and a test suite are part of the build, though the README does not describe a release process for refreshing the bundled file.

The package is MIT licensed, per the README and the LICENSE file. That permits use in proprietary software with the licence notice retained. This is a description of the licence text, not legal advice; check the LICENSE file and your own obligations.

## Conclusion

Adopt composer/ca-bundle if your PHP code makes TLS connections and you cannot assume where the system trust store lives, which is the case for most libraries and CLI tools. Do not adopt it if you already ship a CA file you control and pin, or if you need certificate revocation checking, which the library does not perform. Before relying on it, check which path getSystemCaRootBundlePath() returns on each platform you ship to, and confirm the bundled Mozilla file is current in the version you install.

## FAQ

### How do I use the CA bundle in cURL with composer/ca-bundle?

Call CaBundle::getSystemCaRootBundlePath() and check whether the result is a directory. If it is, set CURLOPT_CAPATH; otherwise set CURLOPT_CAINFO to the returned path, as shown in the README's curl example.

### What is a CA bundle file?

It is the file or directory of trusted certificate authority certificates that a TLS client uses to verify a peer. composer/ca-bundle resolves the path to the system one and falls back to a bundled Mozilla CA file when the system one is not usable.

### How do I install composer/ca-bundle?

The README gives a single command, composer require composer/ca-bundle, which installs the package through Composer. PHP 5.3.2 is the stated minimum, with the latest PHP recommended.

### What is a CA bundle in SSL?

It is the set of certificate authority certificates a TLS client trusts when verifying a server. composer/ca-bundle finds the system copy of that set and falls back to a Mozilla copy bundled in the package.

## Sources

- [composer/ca-bundle on GitHub](https://github.com/composer/ca-bundle)
- [Issues](https://github.com/composer/ca-bundle/issues)
- [License: MIT](https://github.com/composer/ca-bundle/blob/main/LICENSE)
- [README](https://github.com/composer/ca-bundle/blob/main/README.md)
- [Releases](https://github.com/composer/ca-bundle/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/composer-ca-bundle
