PHPackages                             binsky/yaac-ng - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. binsky/yaac-ng

ActivePackage[HTTP &amp; Networking](/categories/http)

binsky/yaac-ng
==============

Yet Another ACME client (Next-Gen): a decoupled LetsEncrypt client

v2.0.2(5mo ago)021Apache-2.0PHPPHP ^8.0

Since Feb 13Pushed 5mo agoCompare

[ Source](https://github.com/binsky08/yaac-ng)[ Packagist](https://packagist.org/packages/binsky/yaac-ng)[ RSS](/packages/binsky-yaac-ng/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (2)Versions (19)Used By (0)

yaac-ng - Yet another ACME client (Next-Gen)
============================================

[](#yaac-ng---yet-another-acme-client-next-gen)

Written in PHP, this client aims to be a simplified and decoupled Let’s Encrypt client, based on [ACME V2](https://tools.ietf.org/html/rfc8555). This fork of [afosto/yaac](https://github.com/afosto/yaac) combines some fixes and improvements, as the original project seems nearly unmaintained.

This is **not** a simple drop-in for the `afosto/yaac` package, since I changed namespaces, classes and code structure.

Decoupled from a filesystem or webserver
----------------------------------------

[](#decoupled-from-a-filesystem-or-webserver)

Instead of, for example, writing the certificate to the disk under an nginx configuration, this client just returns the data (the certificate and private key).

Why
---

[](#why)

Almost all clients are coupled to a type of webserver or a fixed (set of) domain(s). This package can be extremely useful in case you need to dynamically fetch and install certificates.

Requirements
------------

[](#requirements)

- PHP 8.0+
- openssl
- [Flysystem](http://flysystem.thephpleague.com/) (any adapter would do) - to store the Lets Encrypt account information

Getting started
---------------

[](#getting-started)

Getting started is easy. First install the client, then you need to construct a flysystem filesystem, instantiate the client and you can start requesting certificates.

### Installation

[](#installation)

Installing this package is done easily with composer.

```
composer require binsky/yaac-ng
```

### Instantiate the client

[](#instantiate-the-client)

To start the client you need 3 things; a username for your Let’s Encrypt account, a bootstrapped Flysystem and you need to decide whether you want to issue `Fake LE Intermediate X1` (staging: `MODE_STAGING`) or `Let's Encrypt Authority X3`(live: `MODE_LIVE`, use for production) certificates.

```
use binsky\yaac\Client;
use League\Flysystem\Local\LocalFilesystemAdapter;
use League\Flysystem\Filesystem;

//Prepare flysystem
$adapter = new LocalFilesystemAdapter('data');
$filesystem = new Filesystem($adapter);

//Construct the client
$client = new Client([
    'username' => 'example@example.org',
    'fs'       => $filesystem,
    'mode'     => Client::MODE_STAGING,
]);
```

While you instantiate the client, when needed a new Let’s Encrypt account is created and then agrees to the TOS.

### Create an order

[](#create-an-order)

To start retrieving certificates, we need to create an order first. This is done as follows:

```
$order = $client->createOrder(['example.org', 'www.example.org']);
```

In the example above the primary domain is followed by a secondary domain(s). Make sure that for each domain you are able to prove ownership. As a result the certificate will be valid for all provided domains.

### Prove ownership

[](#prove-ownership)

Before you can obtain a certificate for a given domain you need to prove that you own the given domain(s). We request the authorizations to prove ownership. Obtain the authorizations for order. For each domain supplied in the create order request an authorization is returned.

```
$authorizations = $client->authorize($order);
```

You now have an array of `Authorization` objects. These have the challenges you can use (both `DNS` and `HTTP`) to provide proof of ownership.

#### HTTP validation

[](#http-validation)

HTTP validation (where serve specific content at a specific url on the domain, like: `example.org/.well-known/acme-challenge/*`) is done as follows:

Use the following example to get the HTTP validation going. First obtain the challenges, the next step is to make the challenges accessible from

```
foreach ($authorizations as $authorization) {
    $file = $authorization->getFile();
    file_put_contents($file->getFilename(), $file->getContents());
}
```

> If you need a wildcard certificate, you will need to use DNS validation, see below

#### DNS validation

[](#dns-validation)

You can also use DNS validation - to do this, you will need access to an API of your DNS provider to create TXT records for the target domains.

```
foreach ($authorizations as $authorization) {
    $txtRecord = $authorization->getTxtRecord();

    //To get the name of the TXT record call:
    $txtRecord->getName();

    //To get the value of the TXT record call:
    $txtRecord->getValue();
}
```

### Self test

[](#self-test)

After exposing the challenges (made accessible through HTTP or DNS) we should perform a self test just to be sure it works before asking Let's Encrypt to validate ownership.

For a HTTP challenge test call:

```
if (!$client->selfTest($authorization, Client::VALIDATION_HTTP)) {
    throw new \Exception('Could not verify ownership via HTTP');
}
```

For a DNS test call:

```
if (!$client->selfTest($authorization, Client::VALIDATION_DNS)) {
    throw new \Exception('Could not verify ownership via DNS');
}
sleep(30); // this further sleep is recommended, depending on your DNS provider, see below
```

With DNS validation, after the `selfTest` has confirmed that DNS has been updated, it is recommended you wait some additional time before proceeding, e.g. `sleep(30);`. This is because Let’s Encrypt will perform [multiple viewpoint validation](https://community.letsencrypt.org/t/acme-v1-v2-validating-challenges-from-multiple-network-vantage-points/112253), and your DNS provider may not have completed propagating the changes across their network.

If you proceed too soon, [Let's Encrypt will fail to validate](https://community.letsencrypt.org/t/during-secondary-validation-incorrect-txt-record/113643).

### Request validation

[](#request-validation)

Next step is to request validation of ownership. For each authorization (domain) we ask Let’s Encrypt to verify the challenge.

For HTTP validation:

```
foreach ($authorizations as $authorization) {
    $client->validate($authorization->getHttpChallenge(), 15);
}
```

For DNS validation:

```
foreach ($authorizations as $authorization) {
    $client->validate($authorization->getDnsChallenge(), 15);
}
```

The code above will first perform a self test and, if successful, will do 15 attempts to ask Let’s Encrypt to validate the challenge (with 1 second intervals) and retrieve an updated status (it might take Let’s Encrypt a few seconds to validate the challenge).

### Get the certificate

[](#get-the-certificate)

Now to know if we can request a certificate for the order, test if the order is ready as follows:

```
if ($client->isReady($order)) {
    //The validation was successful.
}
```

We now know validation was completed and can obtain the certificate. This is done as follows:

```
$certificate = $client->getCertificate($order);
```

We now have the certificate, to store it on the filesystem:

```
//Store the certificate and private key where you need it
file_put_contents('certificate.cert', $certificate->getCertificate());
file_put_contents('private.key', $certificate->getPrivateKey());
```

> To get a seperate intermediate certificate and domain certificate:
>
> ```
> $domainCertificate = $certificate->getCertificate(false);
> $intermediateCertificate = $certificate->getIntermediate();
> ```

### Who is using it?

[](#who-is-using-it)

Are you using this package? I would love to know. Please send a PR to enlist your project or company.

#### Public users of the original afosto/yaac package

[](#public-users-of-the-original-afostoyaac-package)

- [Afosto SaaS BV](https://afosto.com)
- [Aitrex - Free Let's Encrypt SSL Certificate Generator](https://aitrex.com/freessl.php)
- [Web Whales](https://webwhales.nl)
- [do.de](https://www.do.de)
- [punchsalad.com](https://punchsalad.com/ssl-certificate-generator/)
- [Spreadly](https://spreadly.app)
- [SslForWeb](https://sslforweb.com)
- [SslsForFree](https://sslsforfree.com)

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance71

Regular maintenance activity

Popularity7

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~124 days

Recently: every ~236 days

Total

18

Last Release

165d ago

Major Versions

v1.5.3 → v2.0.02025-07-16

### Community

Maintainers

![](https://www.gravatar.com/avatar/ad08414f217bec1136379809c5b78ef77d1c5e2910b175ffaaf2db84accf1e40?d=identicon)[binsky](/maintainers/binsky)

---

Top Contributors

[![bakkerpeter](https://avatars.githubusercontent.com/u/23257320?v=4)](https://github.com/bakkerpeter "bakkerpeter (32 commits)")[![binsky08](https://avatars.githubusercontent.com/u/30630233?v=4)](https://github.com/binsky08 "binsky08 (7 commits)")[![lordelph](https://avatars.githubusercontent.com/u/444004?v=4)](https://github.com/lordelph "lordelph (4 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![gpibarra](https://avatars.githubusercontent.com/u/21188012?v=4)](https://github.com/gpibarra "gpibarra (2 commits)")[![mikemunger](https://avatars.githubusercontent.com/u/654364?v=4)](https://github.com/mikemunger "mikemunger (2 commits)")[![mgilfillan](https://avatars.githubusercontent.com/u/25685838?v=4)](https://github.com/mgilfillan "mgilfillan (2 commits)")[![bessone](https://avatars.githubusercontent.com/u/1089510?v=4)](https://github.com/bessone "bessone (2 commits)")[![CodeAdminDe](https://avatars.githubusercontent.com/u/53627251?v=4)](https://github.com/CodeAdminDe "CodeAdminDe (2 commits)")[![TheDevilOnLine](https://avatars.githubusercontent.com/u/2675104?v=4)](https://github.com/TheDevilOnLine "TheDevilOnLine (1 commits)")[![tipswithpunch](https://avatars.githubusercontent.com/u/47672855?v=4)](https://github.com/tipswithpunch "tipswithpunch (1 commits)")[![xolf](https://avatars.githubusercontent.com/u/8250052?v=4)](https://github.com/xolf "xolf (1 commits)")[![adrorocker](https://avatars.githubusercontent.com/u/1872940?v=4)](https://github.com/adrorocker "adrorocker (1 commits)")[![zagrad](https://avatars.githubusercontent.com/u/1423264?v=4)](https://github.com/zagrad "zagrad (1 commits)")[![aitrex-global](https://avatars.githubusercontent.com/u/129726402?v=4)](https://github.com/aitrex-global "aitrex-global (1 commits)")[![AlexNodex](https://avatars.githubusercontent.com/u/17162626?v=4)](https://github.com/AlexNodex "AlexNodex (1 commits)")[![anupamsahoo](https://avatars.githubusercontent.com/u/1630888?v=4)](https://github.com/anupamsahoo "anupamsahoo (1 commits)")[![Manawyrm](https://avatars.githubusercontent.com/u/748791?v=4)](https://github.com/Manawyrm "Manawyrm (1 commits)")[![redelschaap](https://avatars.githubusercontent.com/u/6915990?v=4)](https://github.com/redelschaap "redelschaap (1 commits)")[![sasani72](https://avatars.githubusercontent.com/u/44818715?v=4)](https://github.com/sasani72 "sasani72 (1 commits)")

---

Tags

encryptv2letsencryptafostoACMEletsacmev2

### Embed Badge

![Health badge](/badges/binsky-yaac-ng/health.svg)

```
[![Health](https://phpackages.com/badges/binsky-yaac-ng/health.svg)](https://phpackages.com/packages/binsky-yaac-ng)
```

###  Alternatives

[afosto/yaac

Yet Another ACME client: a decoupled LetsEncrypt client

245500.4k1](/packages/afosto-yaac)[acmephp/acmephp

Let's Encrypt client written in PHP

649155.1k](/packages/acmephp-acmephp)[daanra/laravel-lets-encrypt

A Laravel package to easily generate SSL certificates using Let's Encrypt

22650.9k](/packages/daanra-laravel-lets-encrypt)[basecrm/basecrm-php

BaseCRM Official API V2 library client for PHP

221.0M](/packages/basecrm-basecrm-php)[romanpitak/dotmailer-api-v2-client

Client library for the dotMailer v2 (REST) API.

21101.0k2](/packages/romanpitak-dotmailer-api-v2-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
