ACMED.TOML(5)
π NAME π
acmed.toml β the ACMEd configuration file, bestie. This is manual page ACMED.TOML(5) from the File Formats Manual, and itβs literally the config file for acmed(8).
β¨ DESCRIPTION β¨
So acmed.toml is the configuration file for acmed(8), and itβs written in TOML format. The allowed elements are all described below β every single one, no skipping, periodt.
π account
Array of tables, each one representing an account on one or several endpoint. Iconic.
contactsarray β Array of tables describing the account holderβs contact information. Each table must have one and only one key-value pair. Possible keys and their associated values are:mailtostring β AmailtoURI as defined by RFC 6068. This URI canβt containhfields, and it canβt contain more than oneaddr-specin thetocomponent.
envtable β Table of environment variables that will be accessible from hooks.external_accounttable β Table containing the information required to bind the account to an external one. Possible fields and values are:identifierstring β ASCII string identifying the key.keystring β Private key encoded inbase64urlwithout padding.signature_algorithmstring β Name of the signature algorithm used to sign the external account binding message sent to the endpoint as defined in RFC 7518. Possible values are:HS256β¨defaultβ©HS384HS512
hooksarray β Names of hooks that will be called during operations on the account storage file. The hooks are guaranteed to be called sequentially in the declaration order.key_typestring β Name of the asymmetric cryptography algorithm used to generate the key pair. Possible values are:ed25519ed448ecdsa_p256β¨defaultβ©ecdsa_p384ecdsa_p521rsa2048rsa4096
namestring β The name the account is registered under. Must be unique.signature_algorithmstring β Name of the signature algorithm used to sign the messages sent to the endpoint as defined in RFC 7518. The default value is derived from the key type. Possible values are:ES256ES384ES512RS256
π certificate
Array of tables, each one representing a certificate that will be requested to a CA.
Heads up, bestie: by default, certificates are identified by the first identifier in the list of identifiers. That means if you reorder the identifiers so a different identifier is first, a new certificate with a new name will be issued. Not me making the rules, Iβm just telling you the rules.
accountstring β Name of the account to use.csr_digeststring β Name of the certificateβs signing request digest algorithm. Possible values are:sha256β¨defaultβ©sha384sha512
directorystring β Path to the directory where certificates and their associated private keys are stored.endpointstring β Name of the endpoint to use.envtable β Table of environment variables that will be accessible from hooks.file_name_formatstring β Template used to build the fileβs name. The template syntax is MiniJinja. See theSTANDARDSsection for a link to the MiniJinja specifications. If not specified, the value defined in theendpointelement, and then theglobalelement, is used. Default is{{ name }}_{{ key_type }}.{{ file_type }}.{{ ext }}. Possible variables are:extstring β File extension. Seecert_file_extandpk_file_ext.file_typestring β Containspkfor the private key file andcrtfor the certificate file.key_typestring β The certificateβs private key type.namestring β The certificateβs name.
hooksarray β Names of hooks that will be called when requesting a new certificate. The hooks are guaranteed to be called sequentially in the declaration order.identifiersarray β Array of tables listing the identifiers that should be included in the certificate along with the challenge to use for each one. Thednsandipfields are mutually exclusive.challengestring β The name of the challenge to use to prove the identifierβs ownership. Possible values are:dns-01http-01tls-alpn-01
dnsstring β The domain name.envtable β Table of environment variables that will be accessible from hooks.ipstring β The IP address.
key_typestring β Name of the asymmetric cryptography algorithm used to generate the certificateβs key pair. Possible values are:ed25519ed448ecdsa_p256ecdsa_p384ecdsa_p521rsa2048β¨defaultβ©rsa4096
kp_reuseboolean β Set whether or not the private key should be reused when renewing the certificate. Default isfalse.nameβ Name of the certificate. Must be unique unless the key type is different. Will be used in logs and in the associated fileβs name. The*,:and/characters will be replaced by an underscore. Default is the first identifier.random_early_renewstring β Period of time before the usual certificate renewal in which the certificate will renew at a random time. This is useful for when you want to even out your certificate orders when youβre dealing with very large numbers of certificates. The format is described in theTIME PERIODSsection. Default is the value defined in the associated endpoint.renew_delaystring β Period of time between the certificate renewal and its expiration date. The format is described in theTIME PERIODSsection. Default is the value defined in the associated endpoint.subject_attributestable β Table where the certificateβs subject attributes are specified. Possible keys, with their RFC 4519 and X.500 equivalents, are:country_nameβ RFC 4519:c; X.500:countryNamegeneration_qualifierβ shown equivalent:generationQualifiergiven_nameβ shown equivalent:givenNameinitialsβ shown equivalent:initialslocality_nameβ RFC 4519:l; X.500:localityNamenameβ shown equivalent:nameorganization_nameβ RFC 4519:o; X.500:organizationNameorganizational_unit_nameβ RFC 4519:ou; X.500:organizationalUnitNamepkcs9_email_addressβ no equivalent shownpostal_addressβ shown equivalent:postalAddresspostal_codeβ shown equivalent:postalCodestate_or_province_nameβ RFC 4519:st; X.500:stateOrProvinceNamestreetβ RFC 4519:street; X.500:streetAddresssurnameβ RFC 4519:sn; X.500:surnametitleβ shown equivalent:title
π°οΈ endpoint
Array of tables where each element defines a Certificate Authority (CA) which may be used to request certificates. Main character energy, honestly.
file_name_formatstring β Template used to build the fileβs name. For detailed documentation, see thefile_name_formatdirective located in thecertificateelement.namestring β The name the endpoint is registered under. Must be unique.rate_limitsarray β Array containing the names of the HTTPS rate limits to apply.random_early_renewstring β Period of time before the usual certificate renewal in which the certificate will renew at a random time. This is useful for when you want to even out your certificate orders when youβre dealing with very large numbers of certificates. The format is described in theTIME PERIODSsection. Default is the value defined in theglobalsection.renew_delaystring β Period of time between the certificate renewal and its expiration date. The format is described in theTIME PERIODSsection. Default is the value defined in theglobalsection.root_certificates(array) β a list of paths to root certs you want dropped into the trust store, bestie. Just paths, that's it πtos_agreed(boolean) β the "yes I pinky promise I read the Terms Of Service" switch. Set it to agree (or don't, and see what happens π).url(string) β the directory URL of the endpoint. Like, the actual address it talks to β¨
π global
The big table of global config options. Set it once, vibe everywhere π«Ά
accounts_directory(string) β where the accounts' private and public keys live. Pick a directory, any directory πcert_file_group(group_name or group_id, string) β the group that'll own newly-created certificate files. Seechown(2)for the deets π₯cert_file_mode(integer) β the permissions to slap on newly-created certificate files. Seechmod(2)for the deets π₯cert_file_user(username or user_id, string) β the user who'll own newly-created certificate files. Seechown(2)again πcert_file_ext(string) β the file extension for certificate files. Yes, you get to pick it πcertificates_directory(string) β where certificates and their associated private keys are stored. Keep 'em together, it's giving organized π¦env(table) β a table of environment variables that hooks can see. Hook fuel β½file_name_format(string) β the template used to build the file's name. For the full breakdown, go look at thefile_name_formatdirective inside the certificate element β it's all documented there πpk_file_group(group_name or group_id, string) β the group that'll own newly-created private-key files. Seechown(2)πpk_file_mode(integer) β permissions for newly-created private-key files. Seechmod(2)πpk_file_user(username or user_id, string) β the user who'll own newly-created private-key files. Seechown(2)πpk_file_ext(string) β file extension for private-key files. Yes, separately from certs. Iconic πΈrandom_early_renew(string) β a window before the usual cert renewal where the cert renews at a random time. Super useful when you want to spread out your cert orders because you're dealing with, like, a truly unhinged number of certificates. Format lives in the βTIME PERIODSβ section. By default this is disabled β or more accurately, the time frame is0πrenew_delay(string) β how long between the renewal and the expiration date. Format's in the βTIME PERIODSβ section. Default:30dβ³root_certificates(array) β array of paths to root certs that get added to the trust store. Same energy as the endpoint-level one, just globally π
π― group
An array of tables that lets you group several hooks into one. A group is treated as a brand-new hook. Found family, but for hooks π«Ά
hooks(array) β the names of the hooks being grouped. They're guaranteed to be called sequentially in the declaration order. No cutting the line π¦name(string) β the name the group registers under. This name counts as a hook name. Must be unique. No duplicates, periodt π
πͺ hook
An array of tables where each element defines a command that gets launched at a defined point. See βWRITING A HOOKβ for more π
allow_failure(boolean) β whether an error return from this hook is allowed or not. If not allowed, a failure here fails the whole certificate request process. Default:false. So yes, it's a big deal πargs(array) β array of strings for the command's arguments. Every single one πcmd(string) β the name of the command that gets launched. The main character β¨name(string) β the name the hook registers under. Must be unique. Again. No repeats πstderr(string) β path to the file where the command's standard error output is written. Catch those Ls on disk πstdin(string) β path to the file that gets written into the command's standard input. Mutually exclusive withstdin_strβ pick one, not both π«stdin_str(string) β a string written straight into the command's standard input. Mutually exclusive withstdinβ again, one or the other π ββοΈstdout(string) β path to the file where the command's standard output is written. Collect those Ws π«type(array) β array of strings. The possible types are exactly:challenge-dns-01challenge-dns-01-cleanchallenge-http-01challenge-http-01-cleanchallenge-tls-alpn-01challenge-tls-alpn-01-cleanfile-post-createfile-post-editfile-pre-createfile-pre-editpost-operation
π include
An array of paths to configuration files you want to include. The path can be relative or absolute β if it's relative, it's relative to the config file that included it π§
Heads up, bestie: if global options overlap, the one from the last included file wins. So if file A includes files B and C, and all three define the same global option, the final value comes from file C. Last one in the group chat gets the final say π
Unix-style globbing is supported. Yep, globbing. Go wild π
π¦ rate-limit
An array of tables where each element defines an HTTPS rate limit. Boundaries, but make it certificates π
name(string) β the name the rate limit registers under. Must be unique. You know the drill by now πnumber(integer) β how many requests are authorized within the time period. The cap π§’period(string) β the period of time during which that max number of requests is authorized. Format's described in the βTIME PERIODSβ section, go check it β°
β¨ WRITING A HOOK β¨
ok so bestie, listen π when you're grabbing a certificate from a CA over ACME, there are literally three steps that are a nightmare to automatize, and we need to talk about them:
- Solving challenges to prove you own every identifier you're including β this one needs you to poke at other services' configs, so it totally depends on how your infra is set up. no universal answer, sorry π
- Restarting every service that uses the cert β same deal, it's all about your setup.
- Archiving β sure, there are a few default methods, but sometimes admins want (or are required) to do it a different way. and honestly? valid.
So, to give you full automation of all three steps without imposing arbitrary restrictions or methods, acmed(8) uses hooks. fundamentally, a hook is a command line template that gets called at a specific moment in the process π that's it, that's the whole vibe. and because it's just a template, you can point it at any executable script or program chilling on the machine and customize the whole process. iconic.
For a given certificate, hooks are guaranteed to be called sequentially, in the order you declared them β¨ so yes, you can absolutely have one hook that depends on another. but β and this is the important part β several certificates may be renewed at the same time π«Ά therefore hooks shall not use globing or any other action that could disrupt hooks called by a different certificate. don't be that person.
A hook has a type, and the type influences both when it gets called and which template variables you get. you can declare several types on one hook β in that case, the hook is invoked whenever one of its types calls for it. when it's actually called, the hook only has access to the template variables for the current type. and if a hook uses a template variable that doesn't exist for the type it's being invoked for? the variable is just empty. no crash, no drama, just empty π
When you're writing a hook, the values of args, stdin, stdin_str, stdout and stderr are treated as template strings, whereas cmd is not. the template syntax is MiniJinja. check the "STANDARDS" section for a link to the MiniJinja specs, iykyk π
the available types and their template variables are down below bestie π
π¦ challenge-dns-01
Invoked when the ownership of an identifier must be proved using the dns-01 challenge. the available template variables are:
challenge(string) β the name of the challenge typeβ¨dns-01β©. mostly used in hooks with multiple types.env(array) β array containing all the environment variables.identifier(string) β the identifier name whose ownership is currently being validated.is_clean_hook(bool) βFalseproof(string) β the content of the proof that must be written to aTXTentry of the DNS zone for the_acme-challengesubdomain.
π§ challenge-dns-01-clean
Invoked once an identifier's ownership has been proven using the dns-01 challenge. this hook is intended to remove the proof, since it's no longer required ποΈ the template variables are strictly identical to those given in the corresponding challenge-dns-01 hook, excepted is_clean_hook, which is set to true.
πΈ challenge-http-01
Invoked when the ownership of an identifier must be proved using the http-01 challenge. the available template variables are:
challenge(string) β the name of the challenge typeβ¨http-01β©. mostly used in hooks with multiple types.env(array) β array containing all the environment variables.file_name(string) β name of the file containing the proof. this is not a full path and does not include the.well-known/acme-challenge/prefix.identifier(string) β the identifier name whose ownership is currently being validated.is_clean_hook(bool) βFalseproof(string) β the content of the proof that must be written tofile_name.
π challenge-http-01-clean
Invoked once an identifier's ownership has been proven using the http-01 challenge. this hook is intended to remove the proof since it's no longer required. the template variables are strictly identical to those given in the corresponding challenge-http-01 hook, excepted is_clean_hook, which is set to true.
π₯ challenge-tls-alpn-01
Invoked when the ownership of an identifier must be proved using the tls-alpn-01 challenge. the available template variables are:
challenge(string) β the name of the challenge typeβ¨tls-alpn-01β©. mostly used in hooks with multiple types.env(array) β array containing all the environment variables.identifier(string) β the identifier name whose ownership is currently being validated.identifier_tls_alpn(string) β the identifier name whose ownership is currently being validated, in a form suitable for the TLS ALPN challenge.is_clean_hook(bool) βFalseproof(string) β plain-text representation of theacmeIdentifierextension that should be used in the self-signed certificate presented when a TLS connection is initiated with the ALPN extension value.acmed(8)will not generate the certificate itself, since it can be done usingtacd(8).raw_proof(string) β the SHA-256 digest of the key authorization, encoded using Base64 URL scheme without padding. this is intended for the use of ACMEd with a TLS-ALPN responder other thantacd(8).
π«Ά challenge-tls-alpn-01-clean
Invoked once an identifier's ownership has been proven using the tls-alpn-01 challenge. this hook is intended to remove the proof since it's no longer required. the template variables are strictly identical to those given in the corresponding challenge-tls-alpn-01 hook, excepted is_clean_hook, which is set to true.
β file-post-create
Invoked after a non-existent file is created. the available template variables are the same as those available for the file-pre-create type.
β file-post-edit
Invoked after an existent file is modified. the available template variables are the same as those available for the file-pre-create type.
π file-pre-create
Invoked before a non-existent file is created. the available template variables are:
env(array) β array containing all the environment variables.file_directory(string) β name of the directory where the impacted file is located.file_name(string) β name of the impacted file.file_path(string) β full path to the impacted file.
π file-pre-edit
Invoked before an existent file is modified. the available template variables are the same as those available for the file-pre-create type.
π post-operation
Invoked at the end of the certificate request process. the available template variables are:
env(array) β array containing all the environment variables.identifiers(string) β array containing the identifiers included in the requested certificate.is_success(boolean) βTrueif the certificate request is successful.key_type(string) β name of the asymmetric cryptography algorithm used to generate the certificate's key pair.status(string) β human-readable status. if the certificate request failed, it contains the error description.certificate_path(string) β path to the file containing the certificate.private_key_path(string) β path to the file containing the private key.
π DEFAULT HOOKS π
Because so many people have the exact same needs, ACMEd ships with a set of hooks that should serve most situations π no need to reinvent the wheel, bestie.
okay but heads up: hook names are unique. so the following names, and any other name starting with those, are reserved and you should not use them π pick something else, there's plenty of room.
gitβ this hook usesgit(1)to archive private keys, public keys and certificates. you can customize the commit username and email by using theGIT_USERNAMEandGIT_EMAILenvironment variables respectively. cute, right? πhttp-01-echoβ this hook is designed to solve thehttp-01challenge. for this purpose, it will write the proof into{{ env.HTTP_ROOT }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}.
the web server must be configured so the file http://{{ identifier }}/.well-known/acme-challenge/{{ file_name }} can be accessed from the CA. yes, that has to actually be reachable, don't skip this part π
if HTTP_ROOT is not specified, it will be set to /var/www.
tls-alpn-01-tacd-tcpβ this hook is designed to solve thetls-alpn-01challenge usingtacd(8). it requirespkill(1)to support the-Foption β οΈ
tacd(8) will listen on the host defined by the TACD_HOST environment variable (default is the identifier to be validated) and on the port defined by the TACD_PORT environment variable (default is 5001).
tacd(8) will store its pid into {{ TACD_PID_ROOT }}/tacd_{{ identifier }}.pid. if TACD_PID_ROOT is not specified, it will be set to /run.
tls-alpn-01-tacd-unixβ this hook is designed to solve thetls-alpn-01challenge usingtacd(8). it requirespkill(1)to support the-Foption β οΈ
tacd(8) will listen on the unix socket {{ env.TACD_SOCK_ROOT }}/tacd_{{ identifier }}.sock. if TACD_SOCK_ROOT is not specified, it will be set to /run.
tacd(8) will store its pid into {{ TACD_PID_ROOT }}/tacd_{{ identifier }}.pid. if TACD_PID_ROOT is not specified, it will be set to /run.
β° TIME PERIODS β°
okay so ACMEd has its own time period format, and itβs vaguely inspired by ISO 8601 but itβs doing its own thing. Periods are formatted as PM[PM...], where M is a case-sensitive character that stands for a length, and P is an integer multiplier for the length right after it. The authorized lengths are:
sβ secondmβ minutehβ hourdβ daywβ week
Those PM couples can be specified multiple times and in any order, no cap.
For example, 1d42s and 40s20h4h2s both represents a period of one day and forty-two seconds. Math girlie slay β¨
π§ TEMPLATE FILTERS π§
MiniJinja already gives you a ton of filters by default, and ACMEd adds these on top:
π rev_labels
Reverts the labels of a domain name (eg: mx1.example.org becomes org.example.mx1).
π FILES π
/var/lib/acmed/accountsβ default accounts private and public keys directory./etc/acmed/acmed.tomlβ defaultacmed(8)configuration file./var/lib/acmed/certsβ default certificates and associated private keys directory.
πΈ EXAMPLES πΈ
The following example defines a typical endpoint, account and certificate for a domain, several subdomains and an IP address. Itβs giving full setup energy π
[[endpoint]]
name = "example name"
url = "https://acme.example.org/directory"
tos_agreed = true
[[account]]
name = "my test account"
contacts = [
{ mailto = "certs@exemple.net" }
]
[[certificate]]
endpoint = "example name"
account = "my test account"
identifiers = [
{ dns = "exemple.net", challenge = "http-01"},
{ dns = "1.exemple.net", challenge = "dns-01"},
{ dns = "2.exemple.net", challenge = "tls-alpn-01", env.TACD_PORT="5010"},
{ dns = "3.exemple.net", challenge = "tls-alpn-01", env.TACD_PORT="5011"},
{ ip = "203.0.113.1", challenge = "http-01"},
]
hooks = ["git", "http-01-echo", "tls-alpn-01-tacd-tcp", "some-dns-01-hook"]
env.HTTP_ROOT = "/srv/http"
It is possible to use echo(1) to solve the http-01 challenge and rm(1) to clean it. mkdir(1) and chmod(1) are used to prevent issues related to file access. Bestie, itβs giving very careful file permissions π«Ά
[[hook]]
name = "http-01-echo-mkdir"
type = ["challenge-http-01"]
cmd = "mkdir"
args = [
"-m", "0755",
"-p", "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge"
]
[[hook]]
name = "http-01-echo-echo"
type = ["challenge-http-01"]
cmd = "echo"
args = ["{{ proof }}"]
stdout = "{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
[[hook]]
name = "http-01-echo-chmod"
type = ["challenge-http-01-clean"]
cmd = "chmod"
args = [
"a+r",
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
[[hook]]
name = "http-01-echo-clean"
type = ["challenge-http-01-clean"]
cmd = "rm"
args = [
"-f",
"{{ env.HTTP_ROOT | default('/var/www') }}/{{ identifier }}/.well-known/acme-challenge/{{ file_name }}"
]
The hooks from the previous example can be grouped in order to reduce the number of hooks to define in the certificate. Group-chat organization, honestly iconic π
[[group]]
name = "http-01-echo-var-www"
hooks = [
"http-01-echo-mkdir",
"http-01-echo-echo",
"http-01-echo-chmod",
"http-01-echo-clean"
]
[[certificate]]
# Some fields omitted
hooks = ["http-01-echo"]
env.HTTP_ROOT = "/srv/http"
It is also possible to use sendmail(8) in a hook in order to notify someone when the certificate request process is done. Notification queen behavior π
[[hook]]
name = "email-report"
type = ["post-operation"]
cmd = "sendmail"
args = [
"-f", "noreply.certs@example.net",
"contact@example.net"
]
stdin_str = """Subject: Certificate renewal {{ 'succeeded' if is_success else 'failed' }} for {{ identifiers.0 }}
The following certificate has {{ '' if is_success else '*not* ' }}been renewed.
identifiers: {% for ident in identifiers %}{% if not loop.first %}, {% endif %}{{ ident }}{% endfor %}
key type: {{ key_type }}
status: {{ status }}"""
π SEE ALSO π
π STANDARDS π
- Tom Preston-Werner, TOML v0.5.0,
https://toml.io/en/v0.5.0, July 2018. - Armin Ronacher, MiniJinja,
https://docs.rs/minijinja/latest/minijinja/syntax/index.html. - M. Jones, JSON Web Algorithms (JWA), RFC 7518, May 2015.
π AUTHORS π
Rodolphe BrΓ©ard β¨rodolphe@breard.tfβ©
okay bye bestie β this page is ACMED.TOML(5) from Debian, dated Mar 17, 2025, footer tea delivered, periodt π
β¨
π¦ where this page comes from
- alpine 3.24: acmed-doc 0.25.0-r0 Β· MIT
- Copyright (c) 2019-2020 Rodolphe BrΓ©ard <rodolphe@breard.tf>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms