You know those bugs that look small at first — but then drag you, willingly, into a rabbit hole? That was my week with Wiki.js, PgBouncer, and PostgreSQL.
I wanted a straightforward goal: have Wiki.js talk to PostgreSQL behind PgBouncer using proper TLS, including server verification and optional client certs.

I thought I knew TLS, but Node.js had a surprise.
The symptoms
The app would repeatedly throw errors like:
ERR_TLS_CERT_ALTNAME_INVALID undefined:undefined
SELF_SIGNED_CERT_IN_CHAIN undefined:undefined
At first glance everything looked fine:
- Certificates had correct CNs
- SANs were present in the server certs
- psql could connect happily
- PgBouncer presented the certs when tested with OpenSSL (using
-starttls postgres)
So why did Wiki.js (Node.js) complain?
The breakthrough
A single line in the Node.js docs solved it:
servernamemust be a hostname, and not an IP address. It’s the value used for SNI.
Node.js constructs the SNI from the servername — which, in library wrappers, comes from the host in the DB connection string. If that host is an IP, or if DNS inside the pod doesn’t resolve the alias used, Node.js cannot match the certificate SAN and fails. I had set an IP for some reasons. That was the bear trap I found myself in for quite a while.
The fix
To get a secure and working setup, I:
- made sure the hostnames were revolved by DNS
- Ensured PgBouncer frontend certs had SAN entries for all hostnames clients used (including K8s service FQDNs).
- Ensured the PostgreSQL server cert had the SANs PgBouncer uses to connect to it.
Mounted CA chain and client certs in the Wiki.js pod and set these env vars: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" - Verified with
openssl s_client -starttls postgres -connect ... -servername <dns>.
Lessons learned
- Node.js is strict: SNI must be a hostname and must match SANs. IPs are not a substitute.
NODE_EXTRA_CA_CERTSis necessary when you use a local CA not present in system stores.- PgBouncer must be configured so its client-facing certificate is what downstream clients (Wiki.js) expect; PgBouncer → Postgres may use a different cert.
Final thoughts
This was a great reminder that TLS is a stack of responsibilities: endpoints, clients, proxies, certificates, SNI, and DNS all must align. Once they do — it’s rock solid. Until then — enjoy the rabbit hole.
Of course you could skip all that security nonsense and land yourself in a real hot spot, but … that is just not me …
PS: A more technical doc ca be found on my wiki.
Happy coding!