tailscale/tailscale · error
register: %w
Error message
register: %w
What it means
obtainCert's call to acme.Client.Register (create/reuse the ACME account) failed with an error other than ErrAccountAlreadyExists (which is tolerated). The %w wraps a golang.org/x/crypto/acme error: unreachable directory URL, TLS/CA-root failure to the ACME endpoint, HTTP 400 for invalid account/contact, or 5xx from the CA.
Source
Thrown at cmd/derper/ipcert.go:389
}
m.mu.Unlock()
}
}
// obtainCert does one ACME issuance flow for ip: it registers the
// account if needed, orders a short-lived profile certificate for the
// IP address identifier, fulfills the HTTP-01 challenges, and installs
// and caches the issued certificate.
func (m *ipCertManager) obtainCert(ctx context.Context, ip netip.Addr) error {
ipStr := ip.String()
var contact []string
if m.email != "" {
contact = []string{"mailto:" + m.email}
}
_, err := m.client.Register(ctx, &acme.Account{Contact: contact}, acme.AcceptTOS)
if err != nil && !errors.Is(err, acme.ErrAccountAlreadyExists) {
return fmt.Errorf("register: %w", err)
}
order, err := m.client.AuthorizeOrder(ctx, acme.IPIDs(ipStr), acme.WithOrderProfile(shortlivedProfile))
if err != nil {
return fmt.Errorf("new order: %w", err)
}
for _, authzURL := range order.AuthzURLs {
if err := m.fulfillAuthz(ctx, authzURL); err != nil {
return err
}
}
order, err = m.client.WaitOrder(ctx, order.URI)
if err != nil {
return fmt.Errorf("waiting for order: %w", err)
}
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {View on GitHub (pinned to cfe32b8be6)
Solutions
- Verify egress: curl https://acme-v02.api.letsencrypt.org/directory from the derper host.
- Install CA roots in containers (apt-get install ca-certificates or equivalent).
- Validate the email/contact argument is a plain address, not mailto:.
- If the account key is suspect, delete acme-account.key to force a clean registration.
Example fix
docker run --rm derper-image curl -sS https://acme-v02.api.letsencrypt.org/directory # before: minimal distroless image -> register: tls: failed to verify certificate # after: add CA certs to the image RUN apk add --no-cache ca-certificates
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight ACME endpoint reachability and CA roots before starting.
func acmeReachable(dirURL string) error {
cl := &http.Client{Timeout: 10 * time.Second}
resp, err := cl.Get(dirURL)
if err != nil { return err }
resp.Body.Close()
if resp.StatusCode >= 500 { return fmt.Errorf("acme directory %s: %d", dirURL, resp.StatusCode) }
return nil
} Type guard
func isACMEError(err error) bool { var e *acme.Error; return errors.As(err, &e) } Try / catch
err := mgr.ObtainCert(ctx, ip)
var ae *acme.Error
if errors.As(err, &ae) && ae.StatusCode/100 == 5 {
// CA-side transient: back off and retry later
} else if errors.As(err, &ae) && ae.StatusCode == 400 {
// bad contact/account: fix config, do not retry
} Prevention
- Install ca-certificates in container images.
- Allow egress to the ACME directory URL in firewall rules.
- Validate the email flag format once at startup.
- Use the CA's staging directory in tests to avoid account-level lockouts.
When it happens
Trigger: newIPCertManager configured (possibly with a custom directoryURL) then certForIP triggers the first issuance: Register POSTs to the CA. Fails when egress to acme-v02.api.letsencrypt.org is blocked, the account key on disk was corrupted/replaced mid-life, the email contact is rejected, or the container lacks CA root certificates.
Common situations: Minimal container images without ca-certificates; firewall/proxy blocking outbound HTTPS; malformed --acme-email; tests pointing directoryURL at a fake ACME server that is not running.
Related errors
- new order: %w
- waiting for order: %w
- finalizing order: %w
- getting authorization: %w
- acme: nonce not found
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/de75e7b986632629.
Report an issue: GitHub.