TLS Configuration
Overview of TLS modes, self-signed defaults, and certificate options for GoodMem deployments
TLS Configuration
This guide explains how GoodMem configures TLS for its REST and gRPC endpoints, how the GoodMem CLI connects to gRPC, and what operators should validate in production deployments.
Endpoints
GoodMem exposes two network endpoints:
- REST API (HTTP/JSON) on
--rest-port(default:8080) - gRPC API (HTTP/2) on
--grpc-port(default:9090)
TLS enablement is configured per endpoint, but the certificate source (self-signed vs. user-supplied cert/key) is shared across any TLS-enabled endpoint. You cannot use different certificates for REST vs. gRPC today.
Defaults (important)
- TLS is enabled for both endpoints by default.
- If TLS is enabled and you do not provide
--tls-cert-file/--tls-key-file, GoodMem (non-FIPS mode) generates an in-memory self-signed certificate at startup. - The self-signed certificate is ephemeral (regenerated on each restart). Do not rely on it for production.
- In FIPS mode (
--fips-mode-enabled), self-signed TLS is not permitted: you must provide a certificate/key pair or disable TLS.
Recommended deployment patterns
Choose one of these approaches for production:
- TLS at GoodMem: provide
--tls-cert-file+--tls-key-fileand leave TLS enabled on the endpoints you expose. - ACME (automatic certificates): let GoodMem obtain and renew certificates from Let's Encrypt or another ACME CA. See ACME (Automatic Certificate Issuance) below.
- TLS upstream (load balancer / ingress / sidecar): run GoodMem on a private network with plaintext enabled (
--tls-disabledor--rest-tls-enabled=false/--grpc-tls-enabled=false) and enforce TLS at the edge.
CLI connection behavior (gRPC)
When the CLI connects to a gRPC address with https:// (or with no scheme), it probes in order:
- TLS with certificate verification.
- TLS accepting self-signed (skips verification).
- Plaintext (h2c) if TLS fails. The first successful mode is used.
If you explicitly set --server=http://..., the CLI uses plaintext only.
Warning: this fallback can hide misconfiguration (e.g., expired certificate, wrong DNS name) by silently connecting with weaker security. In production, validate the server TLS independently (see Validation).
Core TLS Enablement and Overrides
Controls that turn TLS on/off per endpoint or globally. Global disable wins; per-endpoint flags decide whether REST/gRPC use TLS. FIPS is included here because it constrains which TLS materials are allowed.
| Server flag | Environment variable | Description |
|---|---|---|
--tls-disabled | GOODMEM_TLS_DISABLED | Global off switch; forces REST/gRPC to plaintext. |
--rest-tls-enabled | GOODMEM_REST_TLS_ENABLED | Toggle REST TLS (default: true). |
--grpc-tls-enabled | GOODMEM_GRPC_TLS_ENABLED | Toggle gRPC TLS (default: true). |
--fips-mode-enabled | GOODMEM_FIPS_MODE_ENABLED | Enforce FIPS-only crypto; disallows self-signed when TLS is on. |
Additional validation rules:
--tls-disabledforces both endpoints to plaintext.- If TLS is disabled for all endpoints, supplying any TLS material/options (certificate/key/trust/ACME flags) is treated as an error (to avoid silent misconfiguration).
Self-Signed Controls
Applies when TLS is enabled and no user-provided cert/key is supplied. Defaults allow in-memory self-signed unless FIPS mode is enabled.
| Server flag | Environment variable | Description |
|---|---|---|
--tls-self-signed-dev | GOODMEM_TLS_SELF_SIGNED_DEV | Allow self-signed when no cert/key provided (default: true unless FIPS). |
--tls-self-signed-hostnames | GOODMEM_TLS_SELF_SIGNED_HOSTNAMES | SANs for generated self-signed cert (default: localhost,127.0.0.1,::1). |
If you override --tls-self-signed-hostnames, include every hostname/IP clients will use to reach the server, otherwise hostname verification will fail. Provide bare hostnames/IPs only (no scheme, no port).
Self-Signed Certificate Details
The server-generated self-signed identity is created in-memory with a FIPS-compliant JCE provider:
- Key: RSA 2048-bit, generated with the FIPS provider.
- Certificate: X.509 v3, SHA256withRSA, serial is a 128-bit positive random number.
- Validity: 365 days, backdated 60 seconds to avoid clock-skew “not yet valid” errors.
- SANs: controlled by
--tls-self-signed-hostnamesand may include DNS names and/or IP literals. - Extensions: BasicConstraints CA=false (critical), KeyUsage digitalSignature|keyEncipherment (critical), EKU serverAuth (non-critical).
- Keystore: In-memory BCFKS with alias
self, protected by an internal password; no files are written.
User-Supplied Certificates and Trust
Inputs for properly-signed TLS (certificate and key). The same cert/key are used for any TLS-enabled endpoint.
| Server flag | Environment variable | Description |
|---|---|---|
--tls-cert-file | GOODMEM_TLS_CERT_FILE | PEM certificate/chain for user-supplied TLS (requires --tls-key-file). |
--tls-key-file | GOODMEM_TLS_KEY_FILE | PEM private key for user-supplied TLS (requires --tls-cert-file). |
--tls-trust-cert-file | GOODMEM_TLS_TRUST_CERT_FILE | Reserved for future client-auth support; currently unused for inbound TLS. |
Notes:
- The certificate file may contain a chain (leaf + intermediates).
- The private key must be PEM-encoded PKCS#1 or PKCS#8 and is expected to be unencrypted (no passphrase prompting).
- Updating TLS files requires a server restart (no live reload).
- For local-docker installs that started with the default self-signed TLS, see Local TLS with
mkcertfor an in-place migration procedure.
GoodMem does not currently support mutual TLS / client certificate authentication on REST or gRPC.
ACME (Automatic Certificate Issuance)
GoodMem can obtain and automatically renew certificates from an ACME certificate authority such as Let's Encrypt. When ACME is enabled, GoodMem manages the certificate lifecycle for you and uses the issued certificate for any TLS-enabled endpoint, so you do not supply --tls-cert-file/--tls-key-file.
ACME is mutually exclusive with both manual cert/key files and self-signed TLS. Because self-signed dev mode is enabled by default (outside FIPS mode), you must explicitly turn it off with --tls-self-signed-dev=false when enabling ACME, or the server refuses to start.
How it works
- Challenge type: Only the HTTP-01 challenge is supported. GoodMem runs a small HTTP challenge server (default port
80) that the CA contacts to validate each domain. This port must be reachable from the public internet (or from your ACME CA) and must not collide with the REST or gRPC ports. - Wildcards are not supported. Wildcard certificates require the DNS-01 challenge, which GoodMem does not implement. List each fully-qualified domain explicitly.
- Renewal: After the first certificate is obtained, a background scheduler periodically checks for upcoming expiry and renews automatically. By default it renews when a certificate is within 30 days of expiry and checks every 24 hours.
- Storage: Account keys and issued certificates are persisted under
$HOME/.goodmem/etc/ssl/acmeby default, so they survive restarts.
Required settings
When --tls-acme-enabled is set, GoodMem validates the configuration at startup and refuses to start if any of the following is missing or invalid:
- TLS must be enabled on at least one endpoint (
--rest-tls-enabledor--grpc-tls-enabled). - A directory URL, a contact email, and at least one domain must be provided.
- Terms of Service must be explicitly accepted with
--tls-acme-agree-tos(setting it acknowledges the CA's Terms of Service). - No conflicting certificate source may be configured:
--tls-cert-file/--tls-key-filemust not be set, and self-signed dev mode must be disabled with--tls-self-signed-dev=false(it is already disabled when--fips-mode-enabledis set). - If overridden, the renewal threshold and check interval must be positive.
Options
| Server flag | Environment variable | Description |
|---|---|---|
--tls-acme-enabled | GOODMEM_TLS_ACME_ENABLED | Enable ACME certificate management (default: false). |
--tls-acme-directory-url | GOODMEM_TLS_ACME_DIRECTORY_URL | ACME directory URL; must be an absolute http/https URL (required when ACME enabled). |
--tls-acme-email | GOODMEM_TLS_ACME_EMAIL | Contact email for ACME account registration (required when ACME enabled). |
--tls-acme-domains | GOODMEM_TLS_ACME_DOMAINS | Comma-separated list of domains to request; no wildcards (required when ACME enabled). |
--tls-acme-agree-tos | GOODMEM_TLS_ACME_AGREE_TOS | Agree to the ACME CA's Terms of Service; must be true when ACME enabled (default: false). |
--tls-acme-storage-dir | GOODMEM_TLS_ACME_STORAGE_DIR | Directory for ACME account keys and certificates (default: $HOME/.goodmem/etc/ssl/acme). |
--tls-acme-http01-bind | GOODMEM_TLS_ACME_HTTP01_BIND | Bind address for the HTTP-01 challenge server (default: 0.0.0.0). |
--tls-acme-http01-port | GOODMEM_TLS_ACME_HTTP01_PORT | Port for the HTTP-01 challenge server (default: 80); must not collide with the REST or gRPC port. |
--tls-acme-renew-before-days | GOODMEM_TLS_ACME_RENEW_BEFORE_DAYS | Renew when a certificate is within this many days of expiry (default: 30). |
--tls-acme-renew-check-interval-hours | GOODMEM_TLS_ACME_RENEW_CHECK_INTERVAL_HOURS | How often the renewal scheduler checks for expiry, in hours (default: 24). |
Example
Issue a certificate from Let's Encrypt for two domains. The HTTP-01 challenge port (80) must be published so the CA can reach it, and the storage directory should be mounted so issued certificates and account keys survive restarts:
docker run \
-e GOODMEM_TLS_ACME_ENABLED=true \
-e GOODMEM_TLS_ACME_DIRECTORY_URL=https://acme-v02.api.letsencrypt.org/directory \
-e GOODMEM_TLS_ACME_EMAIL=ops@example.com \
-e GOODMEM_TLS_ACME_DOMAINS=api.example.com,grpc.example.com \
-e GOODMEM_TLS_ACME_AGREE_TOS=true \
-e GOODMEM_TLS_SELF_SIGNED_DEV=false \
-e GOODMEM_TLS_ACME_STORAGE_DIR=/etc/goodmem/acme \
-v /host/path/to/acme:/etc/goodmem/acme \
-p 80:80 \
goodmem/server:latest \
...While testing, use the Let's Encrypt staging directory (https://acme-staging-v02.api.letsencrypt.org/directory) to avoid hitting production rate limits, then switch to the production URL once issuance succeeds.
Validation & troubleshooting
REST (curl)
Good endpoints to validate connectivity:
GET /health(always200)GET /readyz(200when ready, otherwise503)GET /livez,GET /startupz
Examples:
- Verified TLS with a custom CA:
curl -v --cacert /path/to/ca.crt https://HOST:8080/readyz
- Self-signed dev TLS (skip verification):
curl -vk https://localhost:8080/readyz
gRPC (grpcurl)
Examples:
- Verified TLS with a custom CA:
grpcurl -cacert /path/to/ca.crt HOST:9090 list
- Self-signed dev TLS:
grpcurl -insecure HOST:9090 list
- Plaintext:
grpcurl -plaintext HOST:9090 list