Skip to content
lauriroPublic

About

Simple ACME client for green certificates

Resources

Stars

9 stars

Watchers

0 watching

Forks

Repository files navigation

certx.sh – Coverage Buy Me A Tea

Simple ACME client for green certificates. Works with Let's Encrypt, Google Trust, ZeroSSL. Just a shell script - requires curl, openssl, and standard Unix utilities (sed, sort, etc.).

~500 lines of POSIX shell includes DNS/DNS-PERSIST/HTTP challenges, multi-server deployment (ssh/ftp), account rollover/deactivation, CAA binding records, EAB, ARI, wildcard/IP/shortlived/alternate certs.

CA-friendly: creates one account and reuses it for all certificates.

Quick Start

curl -O certx.sh/certx.sh
chmod +x certx.sh
./certx.sh domain example.com dns cloudflare YOUR-API-TOKEN
./certx.sh cert mycert 'example.com,*.example.com'
./certx.sh cert mycert order
# Output: mycert.key, mycert.crt

On first run it'll prompt for CA URL and optional email. The account is created once and reused for all certificates.

Validation

# DNS - interactive (you add TXT record manually)
./certx.sh domain example.com dns manual

# Automated DNS validation requires executable script: ./dns-PROVIDER.sh
# Script adds TXT record and outputs cleanup commands to stdout.

# Available providers: cloudflare, digitalocean, linode, zone.eu
curl -O certx.sh/dns-PROVIDER.sh && chmod +x dns-PROVIDER.sh

# DNS-PERSIST - one long-lived TXT record instead of a new one per order
# Prompts once for _validation-persist.example.com, then reuses it for every order
./certx.sh domain example.com dns-persist
./certx.sh domain example.com dns-persist wildcard          # also authorize *.example.com
./certx.sh domain example.com dns-persist wildcard 20270101 # add persistUntil

# HTTP - writes challenge file to webroot (supports ssh://, needs key login for automation)
# Note: Webroot directory `/.well-known/acme-challenge/` should exist and be accessible via HTTP.
./certx.sh domain example.com http /www
./certx.sh domain example.com http ssh://server/www

# IP - only HTTP validation, can mix with domains
./certx.sh ip 203.0.113.1 http ssh://203.0.113.1/www
./certx.sh cert mycert 'example.com,*.example.com,203.0.113.1'

Deployment

Deploy certs to multiple locations (local, SSH, or FTP):

./certx.sh cert mycert key_path /etc/ssl/private/key
./certx.sh cert mycert crt_path /etc/ssl/certs/cert,ssh://server1/etc/ssl/cert,ftp://user:pass@host/etc/ssl/cert
# Happens automatically after cert order/renewal

Verification

Check that a live server really serves the certificate ordered:

./certx.sh cert mycert check_host www.example.com:443       # configure the endpoint once
./certx.sh cert mycert check_host 10.0.0.5:8443             # any host and port
./certx.sh cert mail check_host mail.example.com:587 smtp   # STARTTLS (smtp, imap, ftp, ..)

./certx.sh cert mycert check
# Certificate OK on www.example.com:443: serial=04A1.. notAfter=Nov  1 21:37:45 2026 GMT

renew-all runs the same check on every cert after renewing.

Renewal

./certx.sh cert mycert order              # order new cert ignoring expiry
./certx.sh renew-all                      # renew via ARI, fallback to 20% of validity
./certx.sh renew-all 33%                  # renew at 33% of validity (ignores ARI)
./certx.sh renew-all 7                    # renew 7 days before expiry (ignores ARI)

Note: renew-all only works for certificates that have been successfully ordered at least once manually using cert <name> order. Without arguments, ARI (ACME Renewal Information) is used if the CA supports it. Explicit days/% overrides ARI.

Cron: /etc/cron.daily/certx

#!/bin/sh
MAILTO=admin@example.com
cd /srv/certx
./certx.sh renew-all

Systemd timer:

# /etc/systemd/system/certx.service
[Unit]
Description=Renew SSL certificates
After=network-online.target

[Service]
Type=oneshot
Environment="CERTX_CONF=/etc/certx/certx.conf"
Environment="CERTX_LOG=/var/log/certx/certx.log"
ExecStart=/usr/local/bin/certx.sh renew-all
ExecStartPost=/bin/systemctl reload nginx
# /etc/systemd/system/certx.timer
[Unit]
Description=Renew SSL certificates daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

CAA account and method binding

Publish DNS CAA records (RFC 8657) to let the CA issue only for this account and validation method.

./certx.sh account
# Account URI: https://acme-v02.api.letsencrypt.org/acme/acct/1234567890
#
# CAA records to bind issuance to this account:
#   example.com CAA 0 issue "letsencrypt.org;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456;validationmethods=dns-01"
  • account-rollover keeps the account URI, ca-reset creates a new one.

Other

Wildcards need DNS validation:

./certx.sh cert wildcard '*.example.com,example.com'

Short-lived IP certificate (6-day cert, hourly renewal recommended):

./certx.sh ip 203.0.113.1 http /www
./certx.sh cert myip 203.0.113.1 shortlived
./certx.sh cert myip order

Post-deployment hook:

./certx.sh cert mycert post_hook "systemctl reload nginx"

Retry failed orders: Failed orders leave a order file, that can retried

./certx.sh retry mycert.order-20260201-120000-12345

Account rollover:

./certx.sh account-rollover

EAB required for Google Trust and ZeroSSL, the script will ask credentials:

# Google: gcloud publicca external-account-keys create
# ZeroSSL: curl --data 'email=you@ex.com' https://api.zerossl.com/acme/eab-credentials-email

Environment vars:

CERTX_CONF=/etc/certx-staging.conf ./certx.sh cert mycert order
CERTX_LOG=/var/log/certx.log ./certx.sh renew-all
ORDER_TIMEOUT=1200 ./certx.sh cert mycert order   # seconds to wait for an order to be issued (default 600)

Commands

#-   domain                                         - list configured domains
#-   domain [name] [dns|dns-persist|http] [opts]..  - configure domain validation
#-   domain [name] drop                             - remove domain configuration
#-   ip                                             - list configured IPs
#-   ip [addr] http [opts]..                        - configure IP validation
#-   ip [addr] drop                                 - remove IP configuration
#-   cert                                           - list created certificates
#-   cert [name] [domain,..] [profile]              - configure cert domains and optional CA profile (eg. shortlived)
#-   cert [name] [key_path|crt_path] [paths,..]
#-   cert [name] post_hook [cmd]                    - commands to run after cert deployment
#-   cert [name] chain [N]                          - set alternate cert positional index (1-..)
#-   cert [name] check_host [host:port] [proto]     - live server endpoint to validate
#-   cert [name] order                              - order and deploy named cert
#-   cert [name] check                              - verify live server serves the ordered cert
#-   cert [name] revoke [reason]                    - revoke certificate (reason: 0-10, default: 0)
#-   cert [name] drop                               - remove cert configuration
#-   account                                        - show account URI and CAA records
#-   account-rollover                               - change account key
#-   account-deactivate                             - deactivate account
#-   authz-deactivate [url]                         - deactivate authorization
#-   ca-reset                                       - delete all CA/account configuration
#-   renew-all [days|%]                             - renew via ARI or days/% of validity (default: ARI, 20%)
#-   retry [order-file]                             - retry failed order
#-   help [topic]

Copyright (c) Lauri Rooden <lauri@rooden.ee>
MIT License | GitHub repo | Buy Me A Tea

About

Simple ACME client for green certificates

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages