This blog post explains how to connect to an IBM Cloud Databases for Redis instance from a Node.js application. There is a (small) difference between the connection details needed for an IBM Cloud Databases for Redis instance compared to a local instance of the open-source database. This is due to all IBM Cloud Databases using secured TLS connections with self-signed certificates.
I keep running into this issue (and forgetting how to fix it 🤦♂️), so I'm documenting the solution here to help myself (and others) who might run into it… 🦸♂️
Connecting to Redis (without TLS connections)
Most Node.js application use the redis
NPM library to interact with an instance of the database. This library has a createClient
method which returns an instance of the client. The Node.js application passes a connection string into the createClient
method. This string contains the hostname, port, username and password for the database instance.
const redis = require("redis"),
const url = 'redis://user:secret@localhost:6379/'
const client = redis.createClient(url);
The client fires a connect
event once the connection is established or an error
event if issues are encountered.
IBM Cloud Databases for Redis Service Credentials
IBM Cloud Databases for Redis provide service credentials through the instance management console. Service credentials are JSON objects with connection properties for client libraries, the CLI and other tools. Connection strings for the Node.js client library are available in the connection.rediss.composed
field.
So, I just copy this field value and use with the redis.createClient
method? Not so fast...
IBM Cloud Databases for Redis uses TLS to secure all connections to the Redis instances. This is denoted by the connection string using the rediss://
URL prefix, rather than redis://
. Using that connection string (without further connection properties), will lead to the following error being thrown by the Node.js application.
Error: Redis connection to <id>.databases.appdomain.cloud:port failed - read ECONNRESET
at TCP.onread (net.js:657:25) errno: 'ECONNRESET', code: 'ECONNRESET', syscall: 'read'
If the createClient
forces a TLS connection to be used createClient(url, { tls: {} })
, this error will be replaced with a different one about self-signed certificates.
Error: Redis connection to <id>.databases.appdomain.cloud:port failed failed - self signed certificate in certificate chain
at TLSSocket.onConnectSecure (_tls_wrap.js:1055:34)
at TLSSocket.emit (events.js:182:13)
at TLSSocket._finishInit (_tls_wrap.js:635:8) code: 'SELF_SIGNED_CERT_IN_CHAIN'
Hmmmm, how to fix this? 🤔
Connecting to Redis (with TLS connections)
All connections to IBM Cloud Databases are secured with TLS using self-signed certificates. Public certificates for the signing authorities are provided as Base64 strings in the service credentials. These certificates can be provided in the client constructor to support self-signed TLS connections.
Here are the steps needed to use those self-signed certificates with the client library...
- Extract the
connection.rediss.certificate.certificate_base64
value from the service credentials.
- Decode the Base64 string in Node.js to extract the PEM certificate string.
const ca = Buffer.from(cert_base64, 'base64').toString('utf-8')
- Provide the certificate file string as the
ca
property in thetls
object for the client constructor.
const tls = { ca };
const client = redis.createClient(url, { tls });
- …Relax! 😎
The tls
property is passed through to the tls.connect
method in Node.js, which is used to setup the TLS connection. This method supports a ca
parameter to extend the trusted CA certificates pre-installed in the system. By providing the self-signed certificate using this property, the errors above will not be seen.
Conclusion
It took me a while to work out how to connect to TLS-secured Redis instances from a Node.js application. Providing the self-signed certificate in the client constructor is a much better solution than having to disable all unauthorised TLS connections!
Since I don't write new Redis client code very often, I keep forgetting the correct constructor parameters to make this work. Turning this solution into a blog post will (hopefully) embed it in my brain (or at least provide a way to find the answer instead of having to grep through old project code). This might even be useful to others Googling for a solution to those error messages...
Top comments (0)