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:
- Public Domain: Your domain's DNS A/AAAA records point to your server.
- 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.
- Persistent Storage: A writeable directory to store ACME account keys and certificates (default:
$HOME/.goodmem/etc/ssl/acme) - TLS Enabled: TLS must be enabled for at least one endpoint (REST or gRPC)
- No Port Conflicts: HTTP-01 challenge port (default: port 80), REST port (default: port 8080), and gRPC port (default: port 9090) must not collide.
- Self-signed TLS disabled: Set
--tls-self-signed-dev=falseorGOODMEM_TLS_SELF_SIGNED_DEV=false. This is already the effective default in FIPS mode.
Configuration Options
Required Flags/Variables
| Option | Environment Variable | Description |
|---|---|---|
--tls-acme-enabled | GOODMEM_TLS_ACME_ENABLED | Enable ACME certificate management (boolean, default: false) |
--tls-acme-directory-url | GOODMEM_TLS_ACME_DIRECTORY_URL | ACME directory URL endpoint |
--tls-acme-email | GOODMEM_TLS_ACME_EMAIL | Contact email for certificate notifications |
--tls-acme-domains | GOODMEM_TLS_ACME_DOMAINS | Comma-separated list of domains (e.g., example.com,www.example.com) |
--tls-acme-agree-tos | GOODMEM_TLS_ACME_AGREE_TOS | Agree to CA Terms of Service (boolean, required) |
--tls-self-signed-dev=false | GOODMEM_TLS_SELF_SIGNED_DEV=false | Disable the mutually exclusive self-signed certificate source (not needed in FIPS mode) |
Optional Flags/Variables
| Option | Environment Variable | Default | Description |
|---|---|---|---|
--tls-acme-storage-dir | GOODMEM_TLS_ACME_STORAGE_DIR | $HOME/.goodmem/etc/ssl/acme | Certificate and account key storage |
--tls-acme-http01-bind | GOODMEM_TLS_ACME_HTTP01_BIND | 0.0.0.0 | Bind address for challenge server |
--tls-acme-http01-port | GOODMEM_TLS_ACME_HTTP01_PORT | 80 | Port for HTTP-01 challenge validation |
--tls-acme-renew-before-days | GOODMEM_TLS_ACME_RENEW_BEFORE_DAYS | 30 | Days before expiry to trigger renewal; must be positive |
--tls-acme-renew-check-interval-hours | GOODMEM_TLS_ACME_RENEW_CHECK_INTERVAL_HOURS | 24 | Hours 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/directoryUse this for production deployments. Rate-limited but trusted by all browsers.
Staging (Let's Encrypt)
https://acme-staging-v02.api.letsencrypt.org/directoryUse 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-enabledOption 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.jarOption 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-acmeHow It Works
Initial Certificate Provisioning
On startup, GoodMem initializes ACME with your configuration:
- Challenge Server: Ephemeral HTTP server starts on port 80.
- Domain Validation: Let's Encrypt verifies domain ownership via HTTP-01 challenge.
- 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):
- Check Expiry: If certificate expires within 30 days (configurable), renewal is triggered.
- Challenge Server: Starts ephemeral HTTP server on port 80.
- Renew Certificate: Obtains new certificate from ACME CA.
- Hot-Reload: Atomically swaps certificate without server restart.
- 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-enabledTesting 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-enabledTroubleshooting
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
- Test with Staging First: Always test your configuration with Let's Encrypt staging before production
- Use Strong Security: Protect your ACME storage directory (
$HOME/.goodmem/etc/ssl/acme) - Monitor Renewals: Monitor logs to ensure certificates renew successfully
- Set Contact Email: Ensure your contact email receives important notifications from the CA
- Plan for Maintenance: Schedule server maintenance during off-peak hours if you need to pause ACME renewal checks