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 managment 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

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.

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. Hot-Reload: Atomically swaps certificate without server restart.
  5. Port Released: Challenge server stops, port 80 freed.

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

Port 80 Not Accessible

Error: Failed to start HTTP-01 challenge server

Solution: For Let's Encrypt, ensure public port 80 is open and reaches the challenge server. If GoodMem listens on a different internal port, forward public port 80 to it. A private or test CA configured to validate on another port can use that port directly.

Domain Validation Fails

Error: ACME validation failed for domain

Solution:

  • Verify DNS records point to your server: dig example.com
  • Check that port 80 is reachable: curl http://example.com/.well-known/acme-challenge/test

Rate Limiting

Error: Error: 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