This page explains how to configure TLS between Wiki.js (Node.js), PgBouncer, and PostgreSQL so that certificate validation, SNI, and Node.js TLS behavior are handled correctly.
PgBouncer (frontend) and PostgreSQL (backend) must present certificates containing correct Subject Alternative Names (SANs).
Wiki.js must be provided the CA chain and client certs (if used) and must use a DNS hostname (not an IP) for SNI.
Kubernetes (or host DNS) must resolve the hostname used by Wiki.js.
NODE_EXTRA_CA_CERTS is required for Node.js to trust non-system CAs.
Node.js sets SNI using the servername passed to the TLS API. It must be a DNS hostname (not an IP).If the client uses an IP, Node.js will not match hostnames in SANs and TLS validation will fail, even if IP addresses are listed in SANS!
DB_SSL: "true" DB_SSL_OPTIONS_CA: "/wiki/tls/ca.crt" DB_SSL_OPTIONS_CERT: "/wiki/tls/client.crt" DB_SSL_OPTIONS_KEY: "/wiki/tls/client.key" DB_SSL_OPTIONS_AUTO: "false" NODE_EXTRA_CA_CERTS: "/wiki/tls/ca.crt"
Notes:
DB_SSL_OPTIONS_AUTO (or DB_SSL_OPTIONS JSON) controls whether Wiki.js auto-loads system CAs; set to false when you provide a custom CA.
NODE_EXTRA_CA_CERTS ensures Node.js includes your CA in its trust store. It is required when using self-signed certificates in your chain (quite usual on backend connections).
server_tls_sslmode = verify-ca server_tls_ca_file = /etc/pgbouncer/tls/ca.crt server_tls_cert_file = /etc/pgbouncer/tls/client.crt server_tls_key_file = /etc/pgbouncer/tls/client.key server_tls_protocols = secure
client_tls_sslmode = require client_tls_ca_file = /etc/pgbouncer/tls/ca.crt client_tls_cert_file = /etc/pgbouncer/tls/server.crt client_tls_key_file = /etc/pgbouncer/tls/server.key client_tls_protocols = secure
Notes: The pgbouncer presents itself as server to the client side and as client to the server side in order to decrypt the connection requests. The certificates could be the same on both sides, but it is recommended to have separate ones with proper DNs, SANS and SNIs.
pgbouncer.example.local), include that DNS in the SAN.pgbouncer.namespace.svc.cluster.local).Concatenate intermediate(s) and root in order (closest to leaf first):
cat intermediate.crt root.crt > ca-chain.crt
openssl req -new -nodes -newkey rsa:4096 -keyout pgbouncer.key -out pgbouncer.csr -config csr_san.cnf
Example csr_san.cnf:
[ req ]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[ dn ]
CN = postgres.example.com # or the hostname you want
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 10.0.1.25
DNS.1 = postgres.example.com
DNS.2 = db001.example.com
openssl x509 -req -in pgbouncer.csr -CA intermediate.crt -CAkey intermediate.key -CAcreateserial -out pgbouncer.crt -days 365 -sha256 -extfile sign_san.cnf -extensions v3_req
Example sign_san.cnf:
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 10.0.1.25
DNS.1 = postgres.example.com
DNS.2 = db001.example.com
openssl x509 -noout -text -in pgbouncer.crt | grep -A2 "Subject Alternative Name"
openssl s_client -starttls postgres -connect <pgbouncer-host>:<port> -servername <hostname> -showcerts
openssl s_client -starttls postgres -connect <host>:<port> -servername <hostname> -CAfile /path/to/ca-chain.crt
openssl s_client returns no peer certificate available and unexpected eof, PgBouncer is not accepting TLS on that port or TLS failed to initialize (check logs and permissions).SELF_SIGNED_CERT_IN_CHAIN, ensure NODE_EXTRA_CA_CERTS points to your full chain and DB_SSL_OPTIONS or DB_SSL_OPTIONS_* are set correctly.ERR_TLS_CERT_ALTNAME_INVALID, confirm the DB_HOST is a DNS hostname (NOT IP address!) that appears in the certificate SAN (and is resolvable in the pod).