Skip to content

ACME clients

This page gives, for the common ACME clients, the commands to enroll a server against a PKIFactor certificate profile and to keep it renewed. The server side (profiles, EAB keys, challenges, network) is described in ACME server.

The Enroll a server (ACME kit) button on a profile page generates the same commands for nginx, Apache, HAProxy, IIS and other Linux servers, with the profile's directory URL and, if needed, a new EAB key already filled in. The sheets below explain them and add Kubernetes.

Tested

Tested against PKIFactor in the laboratory: cert-manager with an EAB key (Kubernetes), and the Kemp LoadMaster built-in ACME client (see Kemp LoadMaster).

Not tested against PKIFactor: certbot, win-acme and acme.sh. Their sheets show standard usage of these clients, as generated by the enrollment kit; check them on a test server first.

What every client needs

Item Where it comes from
Directory URL The profile page, Directory URL: https://acme.example.com/acme/<acme_path>/directory
EAB key identifier and HMAC key Administration workspace, Infrastructure > External accounts (EAB), or the enrollment kit. One key per server: you can then stop one without touching the others
Contact e-mail The team that runs the server. It receives the expiry and no-renewal alerts; without it, the alerts go to the profile's notification address, or failing that to the organization's managers, approvers or the administrators, who may not run the server
A challenge allowed by the profile HTTP-01 (PKIFactor reaches the server on port 80), DNS-01 (the client writes a TXT record through your DNS provider's API; the only choice for a wildcard), TLS-ALPN-01 (port 443, few clients)
Trust in the ACME host's certificate If the certificate of acme.example.com comes from a private CA, install that CA on the server, or point the client at it (see each sheet)
A key type the profile allows The client generates the key. If the profile refuses its default key type, finalization fails with badCSR: set the key type explicitly (see each sheet)

Two rules hold for every client:

  • Each server has its own private key and its own account. Never copy a key or an account from one server to another.
  • HTTP-01 does not follow redirects. If port 80 redirects to HTTPS, exclude /.well-known/acme-challenge/ from the redirect, or use DNS-01.

certbot (nginx, Apache)

certbot is the reference client on Linux. Its --nginx and --apache plugins answer HTTP-01, write the TLS configuration of the virtual host and reload the service. A systemd timer renews certificates 30 days before expiry.

  1. Install the client and the plugin for your web server:

    sudo apt-get install -y certbot python3-certbot-nginx      # or python3-certbot-apache
    # RHEL, Rocky, Alma: sudo dnf install -y certbot python3-certbot-nginx (Apache: python3-certbot-apache, service httpd)
    
  2. Register the account with PKIFactor, with the EAB key:

    sudo certbot register --server https://acme.example.com/acme/srv-web/directory \
      --email web-team@example.com --agree-tos --no-eff-email \
      --eab-kid <kid> --eab-hmac-key <hmac>
    
  3. Obtain and install the certificate (HTTP-01):

    sudo certbot --nginx --server https://acme.example.com/acme/srv-web/directory -n --agree-tos \
      --email web-team@example.com -d www.example.com -d api.example.com \
      --deploy-hook 'systemctl reload nginx'
    

    With Apache, use --apache and reload apache2 (or httpd).

  4. Check renewal:

    sudo certbot renew --dry-run
    systemctl list-timers 'certbot*'
    

DNS-01. --manual --preferred-challenges dns lets you add the TXT record by hand, which is only suitable for a test. To automate, install your DNS provider's certbot plugin (for example python3-certbot-dns-cloudflare, python3-certbot-dns-route53), or use acme.sh.

sudo certbot certonly --server https://acme.example.com/acme/srv-web/directory \
  --manual --preferred-challenges dns --agree-tos --email web-team@example.com -d '*.example.com'
Topic certbot option
Private CA for the ACME host Set REQUESTS_CA_BUNDLE=/path/to/ca.pem in the environment of every certbot command, including the renewal timer
Key type --key-type rsa --rsa-key-size 2048, or --key-type ecdsa --elliptic-curve secp256r1
Force a renewal now sudo certbot renew --cert-name www.example.com --force-renewal
Early renewal requested in PKIFactor (ARI) Read by recent certbot versions only; the certbot 2.1 of Debian 12 ignores it: force the renewal instead

win-acme (IIS)

win-acme (wacs.exe) obtains the certificate, imports it into the machine store, updates the IIS binding and creates a scheduled renewal task. No PKIFactor account on the server is needed.

  1. Download the pluggable x64 build from the win-acme site, extract it to C:\acme, and open PowerShell as administrator in that folder.

  2. Obtain the certificate, install it in IIS and bind it (HTTP-01, with EAB):

    .\wacs.exe --source manual --host www.example.com,api.example.com `
      --baseuri https://acme.example.com/acme/srv-web/directory `
      --emailaddress web-team@example.com --accepttos --installation iis `
      --eab-key-identifier <kid> --eab-key <hmac>
    

    If win-acme asks which IIS site should receive the binding, add --installationsiteid <site id>.

  3. Check:

    .\wacs.exe --list
    Get-ScheduledTask -TaskName 'win-acme renew*'
    

With HTTP-01, IIS itself answers on http://<name>/.well-known/acme-challenge/: the site needs an HTTP binding on port 80 that PKIFactor can reach, without an HTTP to HTTPS redirect on that path.

DNS-01. Add your DNS provider's win-acme validation plugin, or two PowerShell scripts that create and delete the TXT record:

--validation script --dnscreatescript C:\acme\dns-create.ps1 --dnsdeletescript C:\acme\dns-delete.ps1
Topic What to do
Private CA for the ACME host Import the CA into the machine's Trusted Root Certification Authorities store
Force a renewal now .\wacs.exe --renew --force

PKIFactor can also deploy certificates to IIS itself, over WinRM, when the key may be generated by PKIFactor: see Windows IIS.

acme.sh (HAProxy and other Linux servers)

acme.sh is a shell client with no dependency. It suits HAProxy, servers without a certbot plugin, and DNS-01 through the API of many DNS providers. It remembers the deployment and reload commands and replays them at each renewal (daily cron, 30 days before expiry by default).

  1. Install, and make PKIFactor the default server for this installation:

    curl https://get.acme.sh | sh -s email=web-team@example.com
    source ~/.bashrc
    acme.sh --set-default-ca --server https://acme.example.com/acme/srv-web/directory
    
  2. Register the account with the EAB key:

    acme.sh --register-account --server https://acme.example.com/acme/srv-web/directory \
      --eab-kid <kid> --eab-hmac-key <hmac>
    
  3. Obtain the certificate:

    # HTTP-01, with the web root served on port 80
    acme.sh --issue --server https://acme.example.com/acme/srv-web/directory -d www.example.com -w /var/www/html
    
    # HTTP-01 without a local web server (acme.sh listens on port 80 itself)
    acme.sh --issue --server https://acme.example.com/acme/srv-web/directory -d www.example.com --standalone
    
    # DNS-01 through the DNS provider's API (OVH example)
    export OVH_AK=... OVH_AS=... OVH_CK=...
    acme.sh --issue --server https://acme.example.com/acme/srv-web/directory --dns dns_ovh -d www.example.com
    

    Replace dns_ovh with your provider's hook (dns_cf, dns_aws, dns_azure, dns_gandi_livedns...). The DNS API credentials stay on the server.

  4. Deploy to HAProxy (one PEM file with key and chain, then reload):

    sudo mkdir -p /etc/haproxy/certs
    DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy/certs DEPLOY_HAPROXY_RELOAD='systemctl reload haproxy' \
      acme.sh --deploy -d www.example.com --deploy-hook haproxy
    

    For any other service, install the files and give the reload command:

    acme.sh --install-cert -d www.example.com \
      --key-file /etc/ssl/private/www.example.com.key \
      --fullchain-file /etc/ssl/certs/www.example.com.pem \
      --reloadcmd 'systemctl reload <service>'
    
  5. Check:

    acme.sh --list
    crontab -l | grep acme.sh
    
Topic acme.sh option
Private CA for the ACME host --ca-bundle /path/to/ca.pem
Key type --keylength 2048 (RSA) or --keylength ec-256 (ECDSA P-256) at --issue
TLS-ALPN-01 --alpn instead of -w or --standalone (acme.sh listens on port 443 during validation); the profile must allow TLS-ALPN-01
Force a renewal now acme.sh --renew -d www.example.com --force

cert-manager (Kubernetes)

cert-manager requests, stores and renews certificates in Kubernetes Secrets, but signs nothing itself: it delegates to an issuer. With an ACME issuer pointed at a PKIFactor profile, the cluster obtains its certificates from your CA, under the profile's policy, and each one appears in the PKIFactor inventory.

  1. Create an EAB key for the cluster (one per issuer), then store its HMAC key in a Secret. For a ClusterIssuer, the Secret goes in cert-manager's namespace:

    kubectl -n cert-manager create secret generic pkifactor-eab --from-literal=hmac=<hmac>
    
  2. Declare the issuer:

    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: pkifactor
    spec:
      acme:
        server: https://acme.example.com/acme/srv-k8s/directory
        email: platform-team@example.com
        externalAccountBinding:
          keyID: <kid>
          keySecretRef:
            name: pkifactor-eab
            key: hmac
        privateKeySecretRef:
          name: pkifactor-acme-account
        solvers:
          - http01:
              ingress:
                ingressClassName: nginx
          # For names not reachable from PKIFactor, use a DNS-01 solver instead:
          # - dns01:
          #     <your DNS provider>
    
  3. Request a certificate:

    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: api-tls
      namespace: prod
    spec:
      secretName: api-tls
      dnsNames:
        - api.internal.example.com
      issuerRef:
        name: pkifactor
        kind: ClusterIssuer
    
  4. Check:

    kubectl get clusterissuer pkifactor        # READY True
    kubectl -n prod get certificate api-tls     # READY True
    kubectl -n prod get secret api-tls          # tls.crt and tls.key
    

cert-manager offers HTTP-01 and DNS-01. For internal names (services, mesh), DNS-01 is usually the only option: the TXT record is written by cert-manager's DNS solver, PKIFactor only reads it (see DNS-01 and internal zones). HTTP-01 requires an ingress that PKIFactor can reach on port 80, with its network declared if the address is private.

Keep the account Secret

The account key lives in the Secret named by privateKeySecretRef. If it is deleted, cert-manager creates a new account key, and a single-use EAB key already consumed refuses it (EAB already used (single-use credential)). Create a new EAB key and update the issuer.

If the ACME host's certificate comes from a private CA, the cluster must trust it; recent cert-manager versions accept a caBundle field in the ACME issuer for this.

Network devices

Some load balancers and appliances have a built-in ACME client. The Kemp LoadMaster's client was tested against PKIFactor with a one-time EAB key, issuing from Microsoft AD CS, including automatic renewal: see Kemp LoadMaster.

Troubleshooting

Symptom Cause and fix
The client refuses the directory with a certificate error The client does not trust the ACME host's certificate: install the private CA, or use the client's CA bundle option
This ACME endpoint requires External Account Binding (eab-kid/eab-hmac-key) Add the EAB key identifier and HMAC key to the registration
External account binding rejected: ... See the EAB rows of the ACME server troubleshooting
identifier '<name>' is outside this account's authorized scope The name is not in the EAB key's scope: a new key with the right scope, and a new account, are needed
Challenge validation failed (HTTP-01) Port 80 not reachable from PKIFactor, a redirect on port 80, the name resolves elsewhere, or a private address not declared on the PKIFactor side
Challenge validation failed (DNS-01) The TXT record is not yet visible from PKIFactor's resolvers; increase the client's propagation wait, or have internal resolvers declared on the PKIFactor side
badCSR at finalization The profile or an issuance policy refused the request, often the key type: set it explicitly. The detail gives PKIFactor's reason: for a blocking issuance policy rule, the message the administrator wrote on the rule; otherwise the backend's reason (in French in this version)
Certificate issued, but the site still serves the old one The reload did not run: check --deploy-hook (certbot), --reloadcmd or --deploy (acme.sh), the IIS binding (win-acme)
Certificate not yet due for renewal (certbot) or Skip, Next renewal time is... (acme.sh) Normal before the renewal date. To renew now, force the renewal (see each sheet)