{"record":{"id":"de75e7b986632629","repo":"tailscale/tailscale","slug":"register-w","errorCode":null,"errorMessage":"register: %w","messagePattern":"register: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/derper/ipcert.go","lineNumber":389,"sourceCode":"\t\t}\n\t\tm.mu.Unlock()\n\t}\n}\n\n// obtainCert does one ACME issuance flow for ip: it registers the\n// account if needed, orders a short-lived profile certificate for the\n// IP address identifier, fulfills the HTTP-01 challenges, and installs\n// and caches the issued certificate.\nfunc (m *ipCertManager) obtainCert(ctx context.Context, ip netip.Addr) error {\n\tipStr := ip.String()\n\n\tvar contact []string\n\tif m.email != \"\" {\n\t\tcontact = []string{\"mailto:\" + m.email}\n\t}\n\t_, err := m.client.Register(ctx, &acme.Account{Contact: contact}, acme.AcceptTOS)\n\tif err != nil && !errors.Is(err, acme.ErrAccountAlreadyExists) {\n\t\treturn fmt.Errorf(\"register: %w\", err)\n\t}\n\n\torder, err := m.client.AuthorizeOrder(ctx, acme.IPIDs(ipStr), acme.WithOrderProfile(shortlivedProfile))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"new order: %w\", err)\n\t}\n\tfor _, authzURL := range order.AuthzURLs {\n\t\tif err := m.fulfillAuthz(ctx, authzURL); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\torder, err = m.client.WaitOrder(ctx, order.URI)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"waiting for order: %w\", err)\n\t}\n\n\tcertKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/derper/ipcert.go#L371-L407","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"docker run --rm derper-image curl -sS https://acme-v02.api.letsencrypt.org/directory\n# before: minimal distroless image -> register: tls: failed to verify certificate\n# after: add CA certs to the image\nRUN apk add --no-cache ca-certificates","handlingStrategy":"retry","validationCode":"// Pre-flight ACME endpoint reachability and CA roots before starting.\nfunc acmeReachable(dirURL string) error {\n    cl := &http.Client{Timeout: 10 * time.Second}\n    resp, err := cl.Get(dirURL)\n    if err != nil { return err }\n    resp.Body.Close()\n    if resp.StatusCode >= 500 { return fmt.Errorf(\"acme directory %s: %d\", dirURL, resp.StatusCode) }\n    return nil\n}","typeGuard":"func isACMEError(err error) bool { var e *acme.Error; return errors.As(err, &e) }","tryCatchPattern":"err := mgr.ObtainCert(ctx, ip)\nvar ae *acme.Error\nif errors.As(err, &ae) && ae.StatusCode/100 == 5 {\n    // CA-side transient: back off and retry later\n} else if errors.As(err, &ae) && ae.StatusCode == 400 {\n    // bad contact/account: fix config, do not retry\n}","preventionTips":["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."],"tags":["go","acme","lets-encrypt","network","derper"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}