Configuration
The published config/certificates.php in full, comments removed:
return [
'default' => env('CERTIFICATES_DRIVER', 'kubernetes'),
'connection' => env('CERTIFICATES_DB_CONNECTION'),
'model' => RoundlyConsulting\Certificates\Models\Certificate::class,
'table' => 'certificates',
'key_type' => env('CERTIFICATES_KEY_TYPE', 'bigint'),
'renewal' => [
'threshold_days' => env('CERTIFICATES_RENEW_THRESHOLD_DAYS', 21),
'queue' => env('CERTIFICATES_RENEW_QUEUE'),
],
'name_prefix' => env('CERTIFICATES_NAME_PREFIX', 'generated-tls-'),
'lock' => [
'name' => env('CERTIFICATES_LOCK_NAME', 'certificates:generate'),
'locked_for_seconds' => env('CERTIFICATES_LOCK_SECONDS', 5),
],
'status_cache' => [
'enabled' => env('CERTIFICATES_STATUS_CACHE', true),
'store' => env('CERTIFICATES_STATUS_CACHE_STORE'),
'ttl' => env('CERTIFICATES_STATUS_CACHE_TTL', 300),
],
'alerts' => [
'enabled' => env('CERTIFICATES_ALERTS', false),
'notifiable' => env('CERTIFICATES_ALERTS_NOTIFIABLE'),
'thresholds' => [
'warning_days' => env('CERTIFICATES_ALERTS_WARNING_DAYS', 30),
'critical_days' => env('CERTIFICATES_ALERTS_CRITICAL_DAYS', 7),
],
'channels' => ['mail'],
'register_check' => env('CERTIFICATES_ALERTS_REGISTER_CHECK', false),
],
'drivers' => [
'kubernetes' => [
'base_url' => env('CERTIFICATES_K8S_BASE_URL', 'https://kubernetes.default.svc'),
'token' => env('CERTIFICATES_K8S_TOKEN'),
'token_path' => env('CERTIFICATES_K8S_TOKEN_PATH', '/var/run/secrets/kubernetes.io/serviceaccount/token'),
'ca_path' => env('CERTIFICATES_K8S_CA_PATH', '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'),
'namespace' => env('CERTIFICATES_K8S_NAMESPACE', 'default'),
'issuer' => env('CERTIFICATES_K8S_ISSUER', 'letsencrypt'),
'issuer_kind' => env('CERTIFICATES_K8S_ISSUER_KIND', 'ClusterIssuer'),
'ingress' => [
'name' => env('CERTIFICATES_K8S_INGRESS_NAME'),
'class' => env('CERTIFICATES_K8S_INGRESS_CLASS', 'nginx'),
],
'service' => [
'name' => env('CERTIFICATES_K8S_SERVICE_NAME'),
'port' => env('CERTIFICATES_K8S_SERVICE_PORT', 80),
],
],
'acme' => [
'directory' => env('CERTIFICATES_ACME_DIRECTORY', 'https://acme-v02.api.letsencrypt.org/directory'),
// Staging: https://acme-staging-v02.api.letsencrypt.org/directory
'contact' => env('CERTIFICATES_ACME_CONTACT'),
'account' => [
'key_type' => env('CERTIFICATES_ACME_KEY_TYPE', 'EC'),
'disk' => env('CERTIFICATES_ACME_ACCOUNT_DISK', 'local'),
'key_path' => env('CERTIFICATES_ACME_ACCOUNT_KEY', 'acme/account.pem'),
'auto_register' => env('CERTIFICATES_ACME_AUTO_REGISTER', true),
],
'solver' => env('CERTIFICATES_ACME_SOLVER'),
'http' => [
'disk' => env('CERTIFICATES_ACME_HTTP_DISK', 'local'),
'path' => env('CERTIFICATES_ACME_HTTP_PATH', 'acme-challenge'),
],
'store' => [
'disk' => env('CERTIFICATES_ACME_STORE_DISK', 'local'),
'path' => env('CERTIFICATES_ACME_STORE_PATH', 'certificates'),
],
'poll' => [
'attempts' => env('CERTIFICATES_ACME_POLL_ATTEMPTS', 30),
'seconds' => env('CERTIFICATES_ACME_POLL_SECONDS', 2),
],
'verify' => env('CERTIFICATES_ACME_VERIFY', true),
],
'filesystem' => [
'disk' => env('CERTIFICATES_FS_DISK', 'local'),
'path' => env('CERTIFICATES_FS_PATH', 'certificates'),
'self_signed' => env('CERTIFICATES_FS_SELF_SIGNED', false),
'self_signed_days' => env('CERTIFICATES_FS_SELF_SIGNED_DAYS', 90),
],
'null' => [],
'array' => [],
],
];General keys
| Key | Env | Default | Purpose |
|---|---|---|---|
default | CERTIFICATES_DRIVER | kubernetes | Driver used when none is named — a drivers key or one registered with extend(). |
connection | CERTIFICATES_DB_CONNECTION | null | Registry DB connection; null uses the model’s default. Override per call with on(). |
model | — | Models\Certificate::class | Registry model; a subclass swaps it everywhere, any other class throws. |
table | — | certificates | Registry table name. |
key_type | CERTIFICATES_KEY_TYPE | bigint | Key type of the certifiable owner morph: bigint, uuid or ulid (case-insensitive; anything else throws). |
renewal.threshold_days | CERTIFICATES_RENEW_THRESHOLD_DAYS | 21 | Days before expiry a certificate counts as expiring. |
renewal.queue | CERTIFICATES_RENEW_QUEUE | null | Queue RenewCertificateJob is dispatched onto. |
name_prefix | CERTIFICATES_NAME_PREFIX | generated-tls- | Prefix of the derived, lowercase DNS-1123 certificate name. '' is a valid prefix — names are then the bare host. |
lock.name | CERTIFICATES_LOCK_NAME | certificates:generate | Prefix of the per-certificate provisioning lock ({lock.name}:{certificate name}). |
lock.locked_for_seconds | CERTIFICATES_LOCK_SECONDS | 5 | The lock’s safety expiry in seconds — keep it above your slowest issuance. |
status_cache.enabled | CERTIFICATES_STATUS_CACHE | true | Cache live provider status reports. |
status_cache.store | CERTIFICATES_STATUS_CACHE_STORE | null | Cache store; null uses the default store. |
status_cache.ttl | CERTIFICATES_STATUS_CACHE_TTL | 300 | Status cache lifetime in seconds. |
alerts.enabled | CERTIFICATES_ALERTS | false | Raise alerts from certificates:check and on lifecycle failures. |
alerts.notifiable | CERTIFICATES_ALERTS_NOTIFIABLE | null | Notifiable FQCN resolved from the container — it must resolve to a stored Eloquent model, so bind the class to its record (an unbound class builds an unsaved model, which throws); beats the certificate’s owner. |
alerts.thresholds.warning_days | CERTIFICATES_ALERTS_WARNING_DAYS | 30 | Warning band of the expiry check. |
alerts.thresholds.critical_days | CERTIFICATES_ALERTS_CRITICAL_DAYS | 7 | Critical band — the check fails inside it. |
alerts.channels | — | ['mail'] | Channels of the registry-wide check. |
alerts.register_check | CERTIFICATES_ALERTS_REGISTER_CHECK | false | Register one registry-wide expiry check at boot. |
Driver keys are covered on the ACME & Let’s Encrypt, Kubernetes & cert-manager and Filesystem driver pages. The null and array drivers take no options.
Environment
The common knobs are env-driven, so you rarely need to publish the file:
CERTIFICATES_DRIVER=acme
CERTIFICATES_RENEW_THRESHOLD_DAYS=21
CERTIFICATES_RENEW_QUEUE=certificates
CERTIFICATES_STATUS_CACHE_TTL=300
CERTIFICATES_ALERTS=true
CERTIFICATES_ALERTS_WARNING_DAYS=30
CERTIFICATES_ALERTS_CRITICAL_DAYS=7Every bool switch accepts the usual env spellings — true/false, 1/0, on/off, yes/no — so CERTIFICATES_ALERTS=1 turns alerts on and CERTIFICATES_STATUS_CACHE=off turns the cache off. Not set — absent, null or blank ('', what KEY= in .env gives) — reads as the default; anything else (a typo such as disabled) throws InvalidConfigurationException naming the key instead of quietly reading as the default.
Strict config reads
Every other value is read just as strictly. Not set (absent, null or blank) reads as the default; a present value of the wrong shape throws InvalidConfigurationException naming the key:
- Integers take an int or a canonical integer string (env values arrive as strings), so CERTIFICATES_RENEW_THRESHOLD_DAYS=five, '1.5' or a value out of range throws — never a silent 0. Bounds: renewal.threshold_days, lock.locked_for_seconds, status_cache.ttl, drivers.acme.poll.* and drivers.filesystem.self_signed_days ≥ 1; alerts.thresholds.* ≥ 0; drivers.kubernetes.service.port 1–65535.
- String settings (default, table, lock.name, disks, paths, namespace, issuer, …) must be strings, and a blank one is not set and takes its default. The optional ones (connection, queues, stores, tokens, contact, solver, notifiable) must be strings when set, and a blank value is not set, so it stays unset. name_prefix may be '' — it is not read as unset, so names are the bare host.
- drivers.acme.account.key_type is EC or RSA; alerts.channels is a list of channel names; alerts.notifiable must resolve to a stored Eloquent model (bind the class in the container: $this->app->bind(OpsTeam::class, fn () => OpsTeam::query()->firstOrFail())) and drivers.acme.solver to an AcmeChallengeSolver.
- key_type is bigint, uuid or ulid, case-insensitive — unset or blank reads as bigint, anything else throws. The migration reads it, so set it before you migrate.
Locking and TLS verification
lock.* configures a cache lock taken per certificate ({lock.name}:{certificate name}) while it is provisioned — other certificates are never blocked. While it is held, issue() throws ProvisioningInProgressException (the registry row is left untouched) and generate() returns false. Keep lock.locked_for_seconds above your slowest issuance: an ACME order polls for up to poll.attempts × poll.seconds.
drivers.kubernetes.ca_path and drivers.acme.verify read the same way: a CA bundle path, null or an empty value (or true, 1, on, yes) for the system CA bundle, and only a false value (false, 0, off, no) disables TLS verification — not recommended. Any other string is a bundle path (a typo fails the TLS handshake — it never disables verification), and a value that is neither a string nor a boolean (an array, a float) throws InvalidConfigurationException.
Derived certificate names
Every certificate gets a deterministic name: name_prefix plus the primary domain, lowercased and folded into a DNS-1123 subdomain — only a–z, 0–9 and hyphens, a leading *. becomes wildcard-, and an over-long name is cut to 253 characters with a hash suffix. Domains are case-insensitive, so App.Example.com and app.example.com share one name and one registry row. The name is the Kubernetes secret name, the directory name on a store disk and half of the registry’s unique (driver, name) key:
Certificates::certificateName('app.example.com'); // "generated-tls-app-example-com"
Certificates::certificateName('App.Example.com'); // "generated-tls-app-example-com" — the same certificate
Certificates::certificateName('*.Example.com'); // "generated-tls-wildcard-example-com"Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.