ZeroCert is a decentralized ACME client / DNS-01 server that enables multiple hosts to obtain and renew SSL/TLS certificates for the same domain without relying on central storage. It leverages DNS glue records for peer discovery and parallel querying, removing the need for manual coordination.
This approach is suitable for a small number of nodes deployed in a simple environment with no dependencies. All participating hosts must be listed in the glue record, which limits this technique to small installations.
- Decentralized ACME Client – Eliminates central storage / management for TLS certificates.
- DNS-01 Challenge Handling – Uses glue records to discover other servers handling the domain and query all peers in parallel to complete the TXT query.
- Wildcard Domain Support — Use DNS-01 challenge to enable wildcard certificate support.
- Certificate Caching & Retrieval – New hosts attempt to fetch the latest certificate from all peers over HTTPS / mTLS. The mTLS authentication is automatically derived from the ACME private key and requires zero configuration.
- Peer Discovery – The client looks up the glue records for the domain to identify other hosts.
- DNS-01 Challenge Coordination – Instead of using a central database, peers query each other in parallel for the required TXT record.
- Certificate Retrieval on Startup – On host startup, it first attempts to fetch an existing certificate from all peers via glue discovery over HTTPS using mTLS and keep the most recent in its cache.
- Automated Renewal – Certificates are automatically renewed and distributed among participating servers.
go get github.com/rs/zerocert
Given a domain example.com
delegated to a list of hosts running the code below, configure the IP of each hosts into the glue record for example.com
.
m := zerocert.Manager{
Domain: "example.com",
Email: "[email protected]",
Reg: "https://acme-v02.api.letsencrypt.org/acme/acct/1234",
Key: []byte("-----BEGIN EC PRIVATE KEY-----
MHcCA...w==
-----END EC PRIVATE KEY-----"),
CacheFile: "/var/cache/example-cert.pem",
}
d := dns.Server{
PacketConn: m.NewDNSListener(nil),
Handler: ...
}
defer ds.Shutdown()
go func() {
err := ds.ActivateAndServe()
done <- err
}()
l, err := net.Listen("tcp", ":443")
defer l.Close()
if err != nil {
return err
}
tl := m.NewTLSListener(l)
s := http.Server {
Handler: ...
}
go func() {
if err := http.Serve(tl, s); err != nil {
log.Print(err)
}
}()
if err := m.LoadOrRefresh(); err != nil {
log.Fatal(err)
}
for {
select {
case <-time.After(24 * time.Hour):
if err := s.loadOrRefresh(); err != nil {
log.Printf(err)
}
}
}
To get ACME user information, run:
./register.sh
MIT License
Contributions are welcome! Feel free to submit issues or pull requests.