Skip to content

cmd ssl

github-actions[bot] edited this page Oct 19, 2026 · 12 revisions

nself ssl

Manage SSL certificates for ɳSelf services and custom domains.

Synopsis

nself ssl <subcommand> [flags]

Description

nself ssl manages the SSL certificates used by nginx to serve HTTPS traffic for all ɳSelf services. Certificates are generated automatically during nself build, but you can use this command to check their status or force regeneration without a full rebuild.

In v1.5 mode, the canonical command path is nself config trust ssl. The shorter spellings below remain compatibility aliases. The ACME client is the default for setup, add and renew; --acme is only needed to select it in v1.4 mode.

Use nself ssl setup to provision wildcard certificates via DNS-01 challenge for the configured base domain. Use nself ssl add to provision a certificate for a single external custom domain and generate the corresponding nginx server block automatically.

Certificates written by ssl setup and ssl add land in ssl/certificates/{domain-safe}/ inside the project directory (dots and colons in the domain are replaced with dashes, e.g. custom.example.com -> custom-example-com), which nginx reads via its ./ssl:/etc/nginx/ssl:ro volume mount.

nself ssl setup

Provisions SSL certificates with DNS-01 validation. In v1.5 mode (NSELF_V15=1) the CLI's own ACME client issues them and --install-cron installs the nself-acme-renew timer; in v1.4 mode certbot does (pass --acme to use the ACME client there too). Supports wildcard certificates for *.domain.

nself ssl setup [flags]

Flags

Flag Default Description
--provider cloudflare DNS provider (cloudflare, route53, digitalocean)
--wildcard false Request a wildcard certificate (*.domain)
--email (from ADMIN_EMAIL) Email address for Let's Encrypt registration
--staging false Use the Let's Encrypt staging environment
--install-cron false Install a systemd timer for automatic renewal (Linux only)

Examples

# Wildcard certificate via Cloudflare DNS
nself ssl setup --provider cloudflare --wildcard

# Single-domain certificate via Route53
nself ssl setup --provider route53 --domain api.example.com

# Staging run (does not consume rate-limit quota)
nself ssl setup --provider cloudflare --staging

nself ssl add

Provisions an SSL certificate for a single custom domain via HTTP-01 challenge (no DNS provider needed). In v1.4 mode certbot issues the certificate; in v1.5 mode the ACME client does. After the certificate is installed, writes an nginx server block to nginx/conf.d/custom-{domain}.conf and reloads nginx.

Certificates are stored in ssl/certificates/{domain-safe}/ (domain with dots/colons replaced by dashes) so nginx can read them at /etc/nginx/ssl/certificates/{domain-safe}/ inside the container.

nself ssl add <domain> [flags]

Flags

Flag Default Description
--upstream (none) Backend service to proxy to (host:port). When omitted, a 200 placeholder response is returned until an upstream is configured.
--acme off Select the CLI's own ACME client over HTTP-01 in v1.4 mode (already the default in v1.5). Uses a pinned lego container and challenge files in ssl/.acme-webroot/. No DNS credential, age or age key is needed.
--dry-run off ACME client: print the resolved stack, lineage, target and webroot; write nothing.
--nginx-container <name> (auto) ACME client: the nginx container to reload. Default: the running nginx service of the served stack.
--agree-tos off ACME client: accept the ACME CA's terms of service when the account is first registered. Without it a terminal asks; a non-terminal run refuses.

Examples

# Add certificate with proxy to an app container on port 3000
nself ssl add custom.example.com --upstream app:3000

# Add certificate without upstream (returns 200 placeholder)
nself ssl add custom.example.com

Same, with the CLI's own ACME client (HTTP-01)

nself ssl add custom.example.com --acme --agree-tos --upstream app:3000


With `--acme` the certificate is recorded as an `http-01` lineage in `ssl/.acme/lineages.json` and renewed by `nself ssl renew --acme` (also through the renewal timer). Before it asks the CA, the CLI writes a probe token into the webroot and fetches it from the served nginx over local HTTP; if nginx does not answer, nothing is issued and the error says to run `nself build` and restart nginx. The challenge location (bare tokens only, GET, no sub-paths, no symlinks) is in the default server, every non-SSL route block and the port-80 block of the custom conf; see [[Guide-SSL-Setup]].

ACME add refuses before issuance while a saved certbot hook writes into the served or project `ssl/` directory, including with `--dry-run`. Remove the hook lines or deploy scripts named in the error, then retry. Before issuing and installing a certificate, it disables and stops `nself-ssl-renew.timer` and rechecks the hooks so certbot cannot overwrite the ACME generation. If retirement fails, no certificate is issued or installed; the error names the required manual step. A dry run only reports whether the timer would be disabled. Renewal then uses the ACME timer installed by `ssl setup --acme --install-cron`, or `ssl renew --acme` run manually.

The generated conf file (`nginx/conf.d/custom-custom-example-com.conf`) includes:

- HTTP-to-HTTPS redirect on port 80
- TLS on port 443 with HTTP/2
- Security headers: `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Strict-Transport-Security`
- `proxy_pass` block (when `--upstream` is set) or placeholder `return 200`

## ACME setup and renewal

The CLI's own ACME client (DNS-01) issues, adopts and renews the certificates nginx serves, without host certbot. It runs a pinned `lego` container once per issuance through the docker funnel (no Docker socket). In v1.5 mode (`NSELF_V15=1`) `ssl setup`, `ssl add` and `ssl renew` use this client without `--acme`, and an ACME failure carries E470 (issuance), E471 (install or served-certificate check) or E472 (DNS credential missing) instead of E151. In v1.4 mode the certbot flows above are unchanged and `--acme` is additive.

In v1.5 mode:

nself config trust ssl setup [--provider cloudflare|route53|digitalocean] [--wildcard] [--email ] [--agree-tos] [--dry-run] [--install-cron] nself config trust ssl setup --adopt-certbot[=

] --dns-credential-file [--challenge dns-01] [--lineage ] [--dry-run] nself config trust ssl renew [] [--force] [--staging] [--quiet] [--dry-run]

In v1.4 mode, use `nself trust ssl` and add `--acme` to these commands.
ACME renewal selects managed lineages with 30 days or fewer left, installs the
renewed certificates, reloads the serving proxy and verifies the served
certificate. An optional argument selects a lineage by name. With `--force`,
renew regardless of expiry. In v1.4 certbot mode, renew reloads nginx; a domain
argument also requests certbot renewal when certbot is installed.

Adoption follows a served conf that names a top-level symlink of the ssl dir
(for example `/etc/nginx/ssl/api-task-nself-org/` linking to
`certificates/api-task-nself-org`). The link is followed one hop and only when
it lands on `certificates/<name>` inside the ssl dir; the run prints a note
naming the link. A link is not followed, the note says why, and the lineage is
refused as before, when it is absolute but not under `/etc/nginx/ssl` (the path
nginx sees), contains `..`, chains through another link, points at a directory
that does not exist, resolves outside `certificates/`, or `certificates/` is
itself a symlink. Two lineages whose confs reach one `certificates/<name>`
(through links or directly) are refused even with `--lineage`. Nothing in
the ssl dir is rewritten by this step.

| Flag | Applies to | Description |
|------|-----------|-------------|
| `--acme` | setup, renew | Use the CLI's ACME client instead of certbot (the default in v1.5 mode). In v1.4 mode every flag below needs it. |
| `--dry-run` | setup, renew | Print the resolved served root, ssl dir, nginx dir, nginx container, ssl mount, lineages and targets, then stop. Nothing is written. |
| `--nginx-container <name>` | setup, renew | The nginx container to reload. Default: the running `nginx` service of the served stack. |
| `--agree-tos` | setup, renew | Accept the ACME CA's terms of service when the account is first registered. Without it a terminal asks; a non-terminal run refuses. |
| `--adopt-certbot[=<dir>]` | setup | Adopt the lineages of a certbot tree (default `/etc/letsencrypt`). Never writes certbot's files and never issues; refuses while a certbot hook copies into the served or project `ssl/`, and disables `nself-ssl-renew.timer` afterwards. |
| `--dns-credential-file <file>` | setup | A certbot DNS-plugin INI. Its token is stored in the project secret store by name. |
| `--challenge dns-01\|http-01` | setup | Challenge for converted lineages. A `webroot` lineage with no credential file is adopted as `http-01` when the served nginx answers the challenge location for every name, and refused with a remediation otherwise; the other lineages convert to `dns-01`. |
| `--lineage <name>` | setup | Adopt only this certbot lineage. |
| `--force` | renew | Renew every lineage, not only those with 30 days or fewer left. |
| `--staging` | renew | Issue from the CA's staging directory into `.acme/staging/`. Installs nothing. |
| `--email <addr>` | renew | ACME contact. Default: the stored contact, then `ACME_EMAIL`, then `ADMIN_EMAIL`. |
| `--quiet` | renew | Suppress non-error output (the renewal timer uses it). |
| `--install-cron` | setup | Install `nself-acme-renew.service` and `.timer` (daily 03:30, Linux and systemd). With existing lineages it only installs the units; with `--dry-run` it prints them. First disables the certbot `nself-ssl-renew.timer` if it is enabled or running (refuses with the manual `systemctl` command if it cannot). |

`--dry-run` on `--acme` commands, run on a prod-shaped project (`nself-web/backend`, served by nself-web's nginx):

served root: /opt/nself-web served ssl dir: /opt/nself-web/ssl served nginx dir: /opt/nself-web/nginx nginx container: nself-web-nginx-1 nginx ssl mount: /opt/nself-web/ssl -> /etc/nginx/ssl (whole dir) acme contact: ops@example.org age: /usr/bin/age (key /home/deploy/.config/nself/age-key.txt) lineage api-task-nself-org: dns-01 via cloudflare domains: api.task.nself.org target: certificates/api-task-nself-org [directory, becomes a generation link on install] dry run: nothing written


`nself ssl status` prints a Lineages table (lineage, challenge, provider, expiry, days, status, targets) when `ssl/.acme/lineages.json` exists, and a warning when `/etc/letsencrypt/renewal` also manages one of a lineage's names. A second `--acme` run that starts while another holds `ssl/.acme/.lock` waits up to 10 seconds, then exits with `E151` naming the holder.

In v1.4 mode, ACME refusals use error `E151` with a remediation. In v1.5 mode,
classified failures use `E470`, `E471` or `E472` as described above; unclassified
failures and other preflight refusals retain `E151`. Refusals include an nginx
container that mounts single certificates instead of the whole ssl dir, a mount
of `/etc/nginx/ssl/...` that shadows the whole-dir mount, a target that is not
`certificates/<name>`, a non-ASCII name, a missing `age` or age key, no contact
email, a missing DNS credential, a credential file with no provider, two
providers or a Cloudflare global API key, or an unsupported DNS plugin.
<!-- END PROSE:description -->

## Flags

<!-- BEGIN GENERATED:flags -->
| Flag | Default | Description |
|------|---------|-------------|
| `--help`, `-h` | — | Show help |
<!-- END GENERATED:flags -->

## Subcommands

Use `add` to issue a certificate for one domain, `setup` for DNS-01
provisioning or adoption, `renew` for renewal, and `status` to inspect
certificates. See [[Command-Registry]] for the generated command reference,
or run `nself config trust ssl --help` for the current subcommand list.

## Examples

<!-- BEGIN PROSE:examples -->
```bash
# Check certificate status and expiry
nself ssl status

# Force certificate regeneration
nself ssl renew

# Provision wildcard via Cloudflare
nself ssl setup --provider cloudflare --wildcard --email admin@example.com

# Add custom domain with backend proxy
nself ssl add portal.example.com --upstream portal-app:8080

Sample status output:

Certificate: ssl/cert.pem
  Issued to:  *.localhost, localhost
  Expires:    2027-03-28 (730 days remaining)
  CA trust:   trusted (mkcert CA installed)
  SANs:       localhost, *.localhost, api.localhost, auth.localhost

See Also

← Commands | Home →

Home


Getting Started


Commands


Features


Configuration


Plugins (87 + 10 monitoring)

Free (25)
Pro (62)
Planned (26)
  • plugin-audit
  • plugin-blog
  • plugin-checkout
  • plugin-commerce
  • plugin-drm
  • plugin-export
  • plugin-flow
  • plugin-import
  • plugin-ldap
  • plugin-mailgun
  • plugin-media
  • plugin-oauth-providers
  • plugin-pages
  • plugin-postmark
  • plugin-rate-limit
  • plugin-reports
  • plugin-saml
  • plugin-scheduler
  • plugin-sendgrid
  • plugin-sso
  • plugin-subscription
  • plugin-thumb
  • plugin-transcoder
  • plugin-twilio
  • plugin-waf
  • plugin-watermark

Guides


Architecture


Reference


Machine interface


Licensing


Security


Brand


Operations


Contributing


Admin


Changelog


All commands (21)

Clone this wiki locally