GoodMemGoodMem
ReferenceSecurity

ACME Setup

Configure GoodMem to automatically obtain and renew TLS certificates using ACME (Let's Encrypt or compatible providers).

Setting Up ACME for GoodMem

This guide explains how to configure GoodMem to automatically obtain and renew TLS certificates using ACME (Automatic Certificate Management Environment) via Let's Encrypt or compatible providers.

What is ACME?

ACME is an automated protocol for obtaining TLS certificates from Certificate Authorities (CAs) like Let's Encrypt. It eliminates manual certificate renewal by automatically provisioning certificates for your domain. Additionally, GoodMem's native ACME management renews certificates before expiry and reloads them without requiring the server to restart, so users can enjoy maximal uptime. GoodMem validates domain ownership via HTTP-01 challenges.

Prerequisites

Before enabling ACME, ensure:

  1. Public Domain: Your domain's DNS A/AAAA records point to your server.
  2. Port 80 Access: The HTTP-01 challenge server operates on port 80 by default. If this configuration is left unchanged, the GoodMem process will need explicit permissions to be able to access port 80 during certificate operations.
  3. Persistent Storage: A writeable directory to store ACME account keys and certificates (default: $HOME/.goodmem/etc/ssl/acme)
  4. TLS Enabled: TLS must be enabled for at least one endpoint (REST or gRPC)
  5. No Port Conflicts: HTTP-01 challenge port (default: port 80), REST port (default: port 8080), and gRPC port (default: port 9090) must not collide.
  6. Self-signed TLS disabled: Set --tls-self-signed-dev=false or GOODMEM_TLS_SELF_SIGNED_DEV=false. This is already the effective default in FIPS mode.

Configuration Options

Required Flags/Variables

OptionEnvironment VariableDescription
--tls-acme-enabledGOODMEM_TLS_ACME_ENABLEDEnable ACME certificate management (boolean, default: false)
--tls-acme-directory-urlGOODMEM_TLS_ACME_DIRECTORY_URLACME directory URL endpoint
--tls-acme-emailGOODMEM_TLS_ACME_EMAILContact email for certificate notifications
--tls-acme-domainsGOODMEM_TLS_ACME_DOMAINSComma-separated list of domains (e.g., example.com,www.example.com)
--tls-acme-agree-tosGOODMEM_TLS_ACME_AGREE_TOSAgree to CA Terms of Service (boolean, required)
--tls-self-signed-dev=falseGOODMEM_TLS_SELF_SIGNED_DEV=falseDisable the mutually exclusive self-signed certificate source (not needed in FIPS mode)

Optional Flags/Variables

OptionEnvironment VariableDefaultDescription
--tls-acme-storage-dirGOODMEM_TLS_ACME_STORAGE_DIR$HOME/.goodmem/etc/ssl/acmeCertificate and account key storage
--tls-acme-http01-bindGOODMEM_TLS_ACME_HTTP01_BIND0.0.0.0Bind address for challenge server
--tls-acme-http01-portGOODMEM_TLS_ACME_HTTP01_PORT80Port for HTTP-01 challenge validation
--tls-acme-renew-before-daysGOODMEM_TLS_ACME_RENEW_BEFORE_DAYS30Days before expiry to trigger renewal; must be positive
--tls-acme-renew-check-interval-hoursGOODMEM_TLS_ACME_RENEW_CHECK_INTERVAL_HOURS24Hours between renewal checks; must be positive

ACME Directory Endpoints

Use the appropriate endpoint for your environment:

Production (Let's Encrypt)

https://acme-v02.api.letsencrypt.org/directory

Use this for production deployments. Rate-limited but trusted by all browsers.

Staging (Let's Encrypt)

https://acme-staging-v02.api.letsencrypt.org/directory

Use this for testing before production. Higher rate limits, certificates not trusted by browsers. You can use a utility such as keytool to let your local machine trust Let's Encrypt Staging as a CA.

Other Providers

GoodMem supports ACME-compatible CAs that offer HTTP-01 validation and do not require External Account Binding (EAB). DNS-01, wildcard certificates, and EAB credentials are not supported. Check your provider's documentation for the directory URL.

Quick Start

Option 1: Command-Line Flags

java -jar goodmem-server.jar \
  --tls-acme-enabled \
  --tls-acme-directory-url https://acme-v02.api.letsencrypt.org/directory \
  --tls-acme-email [email protected] \
  --tls-acme-domains example.com,www.example.com \
  --tls-acme-agree-tos \
  --tls-self-signed-dev=false \
  --rest-tls-enabled \
  --grpc-tls-enabled

Option 2: Environment Variables

export GOODMEM_TLS_ACME_ENABLED=true
export GOODMEM_TLS_ACME_DIRECTORY_URL=https://acme-v02.api.letsencrypt.org/directory
export GOODMEM_TLS_ACME_EMAIL=[email protected]
export GOODMEM_TLS_ACME_DOMAINS=example.com,www.example.com
export GOODMEM_TLS_ACME_AGREE_TOS=true
export GOODMEM_TLS_SELF_SIGNED_DEV=false
export GOODMEM_REST_TLS_ENABLED=true
export GOODMEM_GRPC_TLS_ENABLED=true

java -jar goodmem-server.jar

Option 3: Docker Compose

Create ./acme-storage and make it writable by the user in the GoodMem container before starting this service. The current server image runs as UID 65532.

services:
  goodmem:
    image: ghcr.io/pair-systems-inc/goodmem/server:latest
    environment:
      GOODMEM_TLS_ACME_ENABLED: "true"
      GOODMEM_TLS_ACME_DIRECTORY_URL: https://acme-v02.api.letsencrypt.org/directory
      GOODMEM_TLS_ACME_EMAIL: [email protected]
      GOODMEM_TLS_ACME_DOMAINS: example.com,www.example.com
      GOODMEM_TLS_ACME_AGREE_TOS: "true"
      GOODMEM_TLS_ACME_STORAGE_DIR: /tmp/goodmem-acme
      GOODMEM_TLS_SELF_SIGNED_DEV: "false"
      GOODMEM_REST_TLS_ENABLED: "true"
      GOODMEM_GRPC_TLS_ENABLED: "true"
      # Database configuration...
    ports:
      - "80:80"      # HTTP-01 challenge server
      - "8080:8080"  # REST server (HTTPS)
      - "9090:9090"  # gRPC server (TLS)
    volumes:
      - ./acme-storage:/tmp/goodmem-acme

The compose file generated by goodmem system install publishes only the REST and gRPC ports. To retrofit ACME onto that installation, publish public port 80 to the configured challenge port and persist the ACME storage directory. The generated compose file forwards the main ACME environment variables, but not GOODMEM_TLS_ACME_RENEW_BEFORE_DAYS or GOODMEM_TLS_ACME_RENEW_CHECK_INTERVAL_HOURS; add those variables to the server.environment section if you need to override their defaults.

How It Works

Initial Certificate Provisioning

On startup, GoodMem initializes ACME with your configuration:

  1. Challenge Server: Ephemeral HTTP server starts on port 80.
  2. Domain Validation: Let's Encrypt verifies domain ownership via HTTP-01 challenge.
  3. Certificate Issuance: Certificate is issued and stored locally.

The server is now ready with TLS certificates. Both REST and gRPC servers now use the certificates.

Initial ACME provisioning is fail-closed. If GoodMem cannot obtain or load a certificate during startup, the server exits with status 1; it does not fall back to a self-signed certificate.

Automatic Renewal

GoodMem checks for certificate renewal every 24 hours (configurable):

  1. Check Expiry: If certificate expires within 30 days (configurable), renewal is triggered.
  2. Challenge Server: Starts ephemeral HTTP server on port 80.
  3. Renew Certificate: Obtains new certificate from ACME CA.
  4. Port Released: Challenge server stops and frees port 80.
  5. Hot-Reload: Atomically swaps certificate without server restart.

No downtime required. Existing connections continue uninterrupted, and new TLS handshakes use the new certificate.

Configuration Examples

Simple Production Setup

Note the comma-separated string of domains:

java -jar goodmem-server.jar \
  --tls-acme-enabled \
  --tls-acme-directory-url https://acme-v02.api.letsencrypt.org/directory \
  --tls-acme-email [email protected] \
  --tls-acme-domains example.com,api.example.com,cdn.example.com \
  --tls-acme-agree-tos \
  --tls-self-signed-dev=false \
  --tls-acme-storage-dir /opt/goodmem/acme \
  --rest-port 8080 \
  --grpc-port 9090 \
  --rest-tls-enabled \
  --grpc-tls-enabled

Testing with Staging

Note the renewal config usage has been set to a higher frequency for more responsive reflection of changes:

java -jar goodmem-server.jar \
  --tls-acme-enabled \
  --tls-acme-directory-url https://acme-staging-v02.api.letsencrypt.org/directory \
  --tls-acme-email [email protected] \
  --tls-acme-domains example.com \
  --tls-acme-agree-tos \
  --tls-self-signed-dev=false \
  --tls-acme-storage-dir /var/lib/goodmem/acme-staging \
  --tls-acme-renew-before-days 14 \
  --tls-acme-renew-check-interval-hours 6 \
  --rest-tls-enabled \
  --grpc-tls-enabled

Troubleshooting

Challenge Listener Cannot Start

Error: Failed to start HTTP-01 challenge server

Solution: This means GoodMem could not start the local listener. Check whether the configured port is already in use, whether the bind address is valid, and—when binding below port 1024—whether the process has CAP_NET_BIND_SERVICE or runs as root. You can instead bind GoodMem to an unprivileged internal port and forward public port 80 to it. A listener startup failure during initial provisioning is fatal and prevents the server from starting.

Domain Validation Fails

Error: ACME authorization failed for domain: example.com

Solution:

  • Verify DNS records point to your server: dig example.com
  • Ensure public port 80 reaches the configured challenge listener.
  • The listener runs only while GoodMem is issuing or renewing a certificate. To test it, run curl http://example.com/.well-known/acme-challenge/test during an issuance attempt. A reachable listener returns 404 Challenge token not found for this unknown token. Outside an issuance or renewal window, no challenge listener is expected to be running.

Rate Limiting

Error: ACME order creation failed, followed by a CA error: line containing a rate-limit detail such as too many certificates ... already issued

Solution: Let's Encrypt has rate limits. Use the staging endpoint first to test, or wait before retrying. See https://letsencrypt.org/docs/rate-limits/

Port Collision

Error: Detected port collision between configured ports [80] (REST server) and [80] (ACME challenge server)

Solution: Give the REST, gRPC, and challenge listeners distinct ports. For Let's Encrypt, route public port 80 to the configured challenge port if they differ. A private or test CA may instead be configured to validate on the alternate port directly.

Certificate Not Updating

Problem: Certificate isn't renewing even after the renewal interval

Solution:

  • Verify renewal is scheduled: Check logs for "Scheduled ACME renewal checks every X hours"
  • Decrease --tls-acme-renew-check-interval-hours (i.e. increase the check frequency) to ensure checks run frequently during testing
  • Restarting the server runs the same expiry check during startup; it renews only when the certificate is inside the configured renewal window

Best Practices

  1. Test with Staging First: Always test your configuration with Let's Encrypt staging before production
  2. Use Strong Security: Protect your ACME storage directory ($HOME/.goodmem/etc/ssl/acme)
  3. Monitor Renewals: Monitor logs to ensure certificates renew successfully
  4. Set Contact Email: Ensure your contact email receives important notifications from the CA
  5. Plan for Maintenance: Schedule server maintenance during off-peak hours if you need to pause ACME renewal checks

Additional Resources