TLS & Let’s Encrypt Configuration
Goma Gateway supports TLS encryption for securing traffic between clients and the gateway. You can configure TLS certificates in three ways:
- Manual configuration — Provide your own certificate and key files
- Directory-based loading — Load multiple certificates from a directory
- Automatic management — Use Let’s Encrypt (ACME) or HashiCorp Vault (PKI) for automatic issuance and renewal
Manual TLS Configuration
Define TLS certificates globally or per-route by specifying certificate and private key pairs.
Certificate Formats
Certificates and keys can be provided in any of these formats:
| Format | Example |
|---|---|
| File path | /path/to/cert.crt |
| Base64-encoded | LS0tLS1CRUdJTi... |
| Raw PEM content | -----BEGIN CERTIFICATE-----... |
Global Configuration
version: 2
gateway:
tls:
certificates:
# File paths
- cert: /path/to/certificate.crt
key: /path/to/private.key
# Base64-encoded
- cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
# Raw PEM content
- cert: |
-----BEGIN CERTIFICATE-----
<certificate content>
-----END CERTIFICATE-----
key: |
-----BEGIN PRIVATE KEY-----
<private key content>
-----END PRIVATE KEY-----
# Fallback certificate for unmatched hosts
default:
cert: /etc/goma/default-cert.pem
key: /etc/goma/default-key.pem
routes:
- path: /
name: secure-route
hosts: ["example.com"]
backends:
- endpoint: https://backend.example.com
Route-Level Configuration
You can also specify TLS certificates for individual routes:
version: 2
gateway:
routes:
- path: /
name: secure-route
hosts: ["example.com"]
backends:
- endpoint: https://backend.example.com
tls:
certificates:
- cert: /path/to/route-specific-cert.crt
key: /path/to/route-specific-key.key
Note: Route-level certificates take precedence over global certificates for matching hosts.
Directory-Based Certificate Loading
Load multiple certificates from a single directory. Goma matches certificate and key files by filename.
Requirements
- Certificate and key files must share the same base name
- Certificate extensions:
.crt,.cert,.pem - Key extension:
.key
Configuration
version: 2
gateway:
tls:
certsDir: /etc/goma/certs
Example Directory Structure
/etc/goma/certs/
├── example.com.crt # Paired with example.com.key
├── example.com.key
├── api.example.com.crt # Paired with api.example.com.key
├── api.example.com.key
├── wildcard.crt # Paired with wildcard.key
└── wildcard.key
Automatic Certificates with Let’s Encrypt (ACME)
Goma Gateway supports automatic certificate issuance and renewal using ACME providers like Let’s Encrypt.
Prerequisites
- Domain must be publicly accessible
- Port 80 must be available for HTTP-01 challenges (or configure DNS-01)
- Valid email address for ACME registration
Basic Configuration (HTTP-01 Challenge)
version: 2
gateway:
entryPoints:
web:
address: ":80" # Required for HTTP-01 challenge
webSecure:
address: ":443" # HTTPS endpoint
routes:
- path: /
name: my-app
hosts: ["example.com"]
backends:
- endpoint: http://localhost:8080
certManager:
provider: acme
acme:
email: "admin@example.com"
Storage: Certificates and ACME account data are stored in
/etc/letsencryptby default. Mount this as a persistent volume in containerized deployments.
Configuration Options
| Key | Type | Description |
|---|---|---|
email | string | Required. Email for ACME registration and expiry notices |
directoryUrl | string | ACME directory URL. Default: Let’s Encrypt production |
storageFile | string | File to store certificates. Default: acme.json |
challengeType | string | http-01 (default) or dns-01 |
dnsProvider | string | DNS provider for DNS-01 challenge (e.g., cloudflare, route53) |
credentials | object | Provider-specific credentials |
DNS-01 Challenge (Cloudflare Example)
Use DNS-01 when port 80 is unavailable or for wildcard certificates:
version: 2
gateway:
entryPoints:
webSecure:
address: ":443"
routes:
- path: /
name: my-app
hosts: ["*.example.com", "example.com"]
backends:
- endpoint: http://localhost:8080
certManager:
provider: acme
acme:
email: "admin@example.com"
challengeType: dns-01
dnsProvider: cloudflare
credentials:
apiToken: your-cloudflare-api-token
Using the Staging Environment
For testing, use Let’s Encrypt’s staging environment to avoid rate limits:
certManager:
provider: acme
acme:
email: "admin@example.com"
directoryUrl: "https://acme-staging-v02.api.letsencrypt.org/directory"
Warning: Staging certificates are not trusted by browsers. Switch to production (
https://acme-v02.api.letsencrypt.org/directory) for live deployments.
Automatic Certificates with HashiCorp Vault (PKI)
Goma Gateway can issue and renew certificates directly from a HashiCorp Vault PKI secrets engine instead of ACME. This is useful for internal services and private PKI where certificates are signed by your own CA rather than a public authority — no ACME challenge, no inbound port 80, and no public DNS required.
Prerequisites
- A reachable Vault server with the PKI secrets engine enabled (default mount
pki). - A PKI role that permits the domains you intend to issue for.
- A Vault token with permission to call
pki/issue/<role>.
Basic Configuration
version: 2
gateway:
entryPoints:
webSecure:
address: ":443"
routes:
- path: /
name: internal-app
hosts: ["app.internal"]
backends:
- endpoint: http://localhost:8080
certManager:
defaultProvider: vault
providers:
vault:
type: vault
vault:
address: https://vault.example.com # or set VAULT_ADDR
token: "" # prefer the VAULT_TOKEN env var
role: goma-gateway
Credentials: Prefer the standard
VAULT_ADDRandVAULT_TOKENenvironment variables over inlining them in the config file. When set, they take precedence over theaddress/tokenfields.
Configuration Options
| Key | Type | Description |
|---|---|---|
address | string | Required. Vault base URL (e.g. https://vault.example.com). Falls back to VAULT_ADDR. |
token | string | Required. Vault token. Falls back to VAULT_TOKEN. Prefer the env var over the config file. |
role | string | Required. PKI role used to issue certificates (pki/issue/<role>). |
mount | string | PKI secrets engine mount path. Default: pki. |
namespace | string | Vault Enterprise namespace. Falls back to VAULT_NAMESPACE. |
ttl | string | Requested certificate lifetime (e.g. 72h). Default: the PKI role’s TTL. |
storageFile | string | File to persist issued certificates. Default: vault-<provider-name>.json. |
How It Works
For each route host, Goma calls POST <address>/v1/<mount>/issue/<role> with the host as the common name (additional hosts become SANs) and serves the returned leaf certificate together with its issuing CA chain. Certificates are cached to disk and renewed on the same schedule as ACME certificates.
Short-lived certificates: Vault PKI certificates often have short TTLs. Goma renews any certificate within 30 days of expiry, so a certificate with a TTL under 30 days is reissued on each renewal cycle (every 6 hours). This is expected.
Per-Route Provider Selection
The tls.provider field on a Route controls which automatic certificate provider issues its certs.
| Value | Meaning |
|---|---|
unset / "" | Use certManager.defaultProvider. |
none | Opt out — CertManager never requests a cert for this route. Falls back to custom or default cert. |
<provider-name> | Use the named provider from certManager.providers. Unknown names cause a config-load error. |
Excluding a Route (tls.provider: none)
Some routes shouldn’t be issued certs by CertManager — TLS is terminated upstream (Cloudflare, a load balancer), the host isn’t publicly resolvable, or you’ve already provided a route-level certificate. Hitting Let’s Encrypt for those hosts wastes ACME quota and can get your account temporarily banned for repeated failed challenges.
version: 2
gateway:
routes:
- path: /
name: behind-cloudflare
hosts: ["app.example.com"]
tls:
provider: none # CertManager will not request a cert for this route
backends:
- endpoint: http://localhost:8080
certManager:
provider: acme
acme:
email: "admin@example.com"
When tls.provider: none is set, the route’s hosts are never registered with CertManager. Incoming TLS connections are served, in order:
- The route’s own
tls.certificates(if configured) - A matching certificate from
gateway.tls.certificatesorgateway.tls.certsDir - The gateway’s default (self-signed) certificate
Multiple Providers
You can configure several named providers under certManager.providers and let each Route pick one via tls.provider. Providers can be any mix of type: acme and type: vault. Common reasons:
- Public routes use ACME (Let’s Encrypt) while internal routes use Vault (private PKI).
- Some routes need DNS-01 (wildcards, no inbound port 80) while others use HTTP-01.
- Different routes belong to different ACME accounts (separate Let’s Encrypt rate-limit pools).
- One environment uses Let’s Encrypt staging while another uses production.
version: 2
gateway:
routes:
- path: /
name: api
hosts: ["api.example.com"]
tls:
provider: cloudflare-dns # uses DNS-01 with Cloudflare
backends:
- endpoint: http://localhost:8080
- path: /
name: marketing
hosts: ["marketing.example.com"] # tls.provider unset → defaultProvider
backends:
- endpoint: http://localhost:8081
- path: /
name: staging-app
hosts: ["staging.example.com"]
tls:
provider: letsencrypt-staging # uses LE staging directory
backends:
- endpoint: http://localhost:8082
- path: /
name: internal-admin
hosts: ["admin.internal"]
tls:
provider: vault # private PKI, signed by your own CA
backends:
- endpoint: http://localhost:8083
certManager:
defaultProvider: letsencrypt
providers:
letsencrypt:
type: acme
acme:
email: "ops@example.com"
challengeType: http-01
letsencrypt-staging:
type: acme
acme:
email: "ops@example.com"
directoryUrl: "https://acme-staging-v02.api.letsencrypt.org/directory"
cloudflare-dns:
type: acme
acme:
email: "ops@example.com"
challengeType: dns-01
dnsProvider: cloudflare
credentials:
apiToken: "your-cloudflare-api-token"
vault:
type: vault
vault:
address: https://vault.example.com # or set VAULT_ADDR
token: "" # prefer the VAULT_TOKEN env var
role: goma-gateway
Storage layout
Each provider keeps its own certificate cache (and, for ACME, its own account). By default they live under /etc/letsencrypt/:
- The legacy / single-provider config still uses
acme.json. - Named ACME providers default to
acme-<provider-name>.json(e.g.acme-letsencrypt.json,acme-cloudflare-dns.json). - Named Vault providers default to
vault-<provider-name>.json. - Override per provider via
acme.storageFile(orvault.storageFile) if you need a custom path.
Important: in containerized deployments, mount
/etc/letsencrypt/(or your custom path) as a persistent volume. Sharing one storage file between providers will corrupt ACME account state.
Validation
If a Route’s tls.provider doesn’t match any name in certManager.providers (and isn’t "" or none), the gateway refuses to start. This is intentional — silent fallback to the default provider is what causes Let’s Encrypt rate-limit bans when a route is misconfigured.
Backward compatibility
The legacy single-provider shape still works without modification:
certManager:
provider: acme
acme:
email: "admin@example.com"
At load time this is migrated into providers.default (the synthetic LegacyProviderName) with defaultProvider: default. Existing acme.json storage continues to work.
Troubleshooting
Certificate Not Found
Ensure the hostname in your route’s hosts field matches the certificate’s Common Name (CN) or Subject Alternative Names (SANs).
ACME Challenge Failures
- HTTP-01: Verify port 80 is accessible and not blocked by firewalls
- DNS-01: Check that API credentials have permission to create TXT records
Certificate Renewal
ACME certificates are automatically renewed before expiration. Ensure the /etc/letsencrypt directory is persistent across container restarts.