Redis Test Connection that shows every step

Why a Redis connection fails, step by step: DNS, SSH tunnel, TCP, TLS, AUTH, RESP3, SELECT. Plus command timeouts, SSH keepalives and OS trust roots.

By Updated

The short version

"Could not connect" is not an error message. "TLS failed: …" is.

Test Connection on a Redis connection now walks the connection one step at a time: name lookup, tunnel or proxy, TCP, TLS, AUTH, HELLO and PING, SELECT and CLUSTER INFO. It logs each step as it finishes, and when one fails, that step heads the error. Along the way every SQLly connection got a name, RESP3, a command timeout and SSH keepalives, and TLS gained an option to trust your computer's certificate store. We also fixed two TLS bugs that turned up in the process.

Why connection errors are hard

Reaching a production Redis server often means a chain: a DNS name, an SSH bastion or a SOCKS proxy, a TCP port, a TLS handshake against a private CA, an ACL user, a protocol negotiation and a logical database. Any link can break, and most tools report all of them the same way, as a timeout or a reset. read ECONNRESET can mean a TLS server got a plaintext hello, a proxy dropped you, or a firewall cut an idle connection. You end up debugging with openssl s_client and redis-cli in another window.

So the test does what you would do by hand, in order, and tells you how far it got.

The staged log

Press Test in the connection editor and open the diagnostic log with ⌘⇧L (Ctrl+Shift+L). With Preferences › Advanced › Log level set to Verbose you see every step. A failing step is logged at Errors only too. Each line is the step, its verdict, how long it took, and what it found.

StepWhat it tells you
ResolveThe addresses the host name resolves to (skipped behind a tunnel, whose far end resolves it).
Tunnel / proxyThe SSH hops, proxy or Kubernetes port-forward, and the local port it opened.
TCPWhether anything accepts the connection on the port.
TLSVersion, cipher, server name sent, how many certificates were presented, and whether the chain verifies against what the connection trusts. This is reported even in Require mode.
AUTHThe user you signed in as, or that the server wants a password you didn't give.
HELLO / PINGRESP3 or RESP2, the product, version, mode and role, and the PING round trip.
SELECTWhether the logical database is accepted (a cluster only has database 0).
CLUSTER INFOStandalone server or cluster node, with state and size. It never fails the test.
SessionThe real query session opens, startup commands included.

Here is what a passing test through an SSH bastion to a TLS server with a private CA looks like. The line format is the one SQLly writes; the host names, timings and values are illustrative.

Resolve skipped (0 ms): cache.internal.example is resolved on the far side of the tunnel
Tunnel / proxy ok (212 ms): SSH deploy@bastion.example.com:22 → cache.internal.example:6380 open at 127.0.0.1:53122
TCP ok (1 ms): connected to the tunnel's local end 127.0.0.1:53122
TLS ok (38 ms): TLS 1.3, TLS13_AES_256_GCM_SHA384, SNI cache.internal.example, 2 certificate(s) presented; chain and host name verified against the CA file /Users/dana/certs/corp-ca.pem and the bundled public roots
AUTH ok (4 ms): signed in as app-reader
HELLO / PING ok (5 ms): RESP3 (redis 7.4.11, standalone master); PING 2.1 ms
SELECT ok (2 ms): database 3
CLUSTER INFO ok (2 ms): standalone server (cluster support disabled)
Session ok (61 ms): RESP3 session ready as sqlly:orders-cache:48213

When a step fails, the trace stops there and the connection editor's error starts with that step's name. A TLS server whose certificate was issued by a CA the connection doesn't trust (again illustrative):

Resolve ok (3 ms): cache.example.com → 10.0.4.17
Tunnel / proxy skipped (0 ms): direct connection
TCP ok (2 ms): connected to 10.0.4.17:6380
TLS FAILED (21 ms): TLS 1.3, TLS13_AES_128_GCM_SHA256, SNI cache.example.com, 1 certificate(s) presented; the certificate does not verify against the bundled public roots: invalid peer certificate: UnknownIssuer

Connection error: TLS failed: TLS 1.3, …; the certificate does not verify against the bundled public roots: …

That one line tells you the network is fine, the server speaks TLS, and the fix is a CA file or the OS-roots option below, not a firewall ticket. A wrong password says AUTH failed: WRONGPASS …. A non-zero database on a cluster says the cluster has only database 0 and to clear the Database field. Behind a tunnel, a failure after the tunnel opened adds that the tunnel accepted the connection, so the thing to check is whether the server is reachable from the far end.

Screenshot to comeredis-test-connection-log.webp — the diagnostic log window at Verbose after Test on the TLS fixture connection, showing one line per step from Resolve to Session with the TLS line naming version, cipher, server name and the chain verdict
Test Connection on a TLS Redis server: one line per step, so you can see exactly how far it got.
Screenshot to comeredis-test-connection-tls-failed.webp — the connection editor after Test on the TLS fixture in Verify full with no CA file, with the error headed "TLS failed:" and naming the bundled public roots
The failing step heads the error: here the certificate chain, not the network.

The same trace runs in two more places. On a Sentinel connection, Resolve asks the sentinels which master to trace. In the Cluster Topology… view, Trace runs it against every node and checks each node's certificate against the address that node announces.

How every connection now behaves

RESP3, with identical results

SQLly asks for RESP3 with HELLO 3 and stays on RESP2 when the server doesn't offer it. RESP3 replies have different shapes (maps instead of flat arrays, doubles instead of strings), so we made sure the grid doesn't change: hashes, configuration listings, scored ranges and scores come out in the same rows and spellings on both protocols. A result shouldn't depend on a negotiation you never see. SQLLY.SERVERINFO and Test Connection's server details say which protocol a connection uses, and whether it's encrypted.

A name on every connection

Query tabs, the Profiler's MONITOR connection and the Pub/Sub viewer all run CLIENT SETNAME with sqlly:<connection name>:<process id>, for example sqlly:prod-cache-eu:48213. In CLIENT LIST or the Activity Monitor's Sessions tab you can tell which connections are SQLly's, which saved connection opened them, and which one is the Profiler. A server or ACL that refuses CLIENT SETNAME just leaves them unnamed. On a server without RESP3, the Pub/Sub viewer's connection can't be named.

A command timeout that never re-runs anything

A command that gets no reply within the connection's Query timeout (30 seconds when blank, no limit when 0) fails with a message that names it:

HGETALL got no reply within 30 s (the connection's command timeout; set the query timeout to 0 for no limit). It may still be running on the server, so it was not retried.

Blocking commands get the wait you asked for on top, so BLPOP jobs 20 is never cut short at 30 seconds of silence. The Profiler and Pub/Sub viewer wait without a limit, because silence is normal there. The important decision is the last sentence of that message. A command that timed out may still finish on the server, so SQLly never runs it again for you. An automatic retry of INCR or LPUSH is a duplicate write.

SSH tunnels that stay up, and come back

A reported issue against Redis Insight, #6292, asks for an SSH keepalive option because idle tunnels get cut by firewalls and NAT gateways. That's a real-world problem for every tool that tunnels, so SQLly's tunnels now send an SSH keepalive after 30 seconds of silence. Set Keepalive (seconds) in the tunnel settings to change the interval, or 0 to turn keepalives off.

If the SSH session drops anyway, SQLly notices and the next run opens a fresh tunnel instead of using a dead local port. That run's Messages say:

Tunnel re-established: the SSH session had dropped, so a new tunnel was opened for this run. Nothing was re-run; if a command was running when the tunnel dropped, check whether it took effect before running it again.

Same rule as the timeout: reconnect automatically, never repeat a command automatically.

TLS: trusting what you already trust

"Also trust the OS keychain roots"

In Verify CA and Verify full, a Redis connection checks the server against the certificate authorities built into SQLly, the same list on every machine. Many companies install their own CA on employees' computers instead of handing out PEM files. Tick Also trust the OS keychain roots and the connection also trusts the macOS keychain, the Windows certificate store or the Linux CA bundle, so it verifies without a CA file. The option never loosens anything. If the store can't be read, the built-in list stays in force, and a certificate nobody trusts is still refused.

A reported issue against Redis Insight, #6472, describes Entra ID sign-in to Azure Managed Redis failing behind a TLS-inspecting proxy, which is the situation where a company CA lives in the OS store. To be precise about what our option covers: it applies to the Redis connection's own TLS. We haven't tested SQLly's Entra sign-in behind a TLS-inspecting proxy, and we haven't run against a live Azure Managed Redis, ElastiCache or MemoryDB instance yet.

Pasting rediss:// never lowers verification

Pasting a rediss:// URL turns TLS on as Require, because the scheme says "encrypted", not "verified". If the connection already uses Verify CA or Verify full, the paste never lowers it. A redis:// URL means plaintext. Losing TLS on the way in is worse than losing it loudly. A reported Redis Insight issue, #6019, describes Redis Cloud autodiscovery importing databases without TLS, which then failed with read ECONNRESET. SQLly's importer for Redis Insight exports always keeps the TLS settings and certificates.

The TLS modes were checked live against a TLS-only Redis 7.4.11 server: rediss:// maps to Require; Require, Verify CA with a CA file and Verify full with a CA file all connect and round-trip 00014; and Verify full without the CA file is refused before any command runs.

Two bugs we fixed on the way

  • A CA file replaced the built-in roots instead of adding to them. Adding your private CA to a Redis connection stopped servers with public certificates from verifying. A Redis CA file now adds to the built-in list, as it already did for PostgreSQL.
  • Verify full through an SSH tunnel or proxy checked the wrong name. It checked the certificate against the tunnel's local address, so a correctly issued certificate for cache.internal.example failed against 127.0.0.1. It now checks the server name on the connection, and Test Connection's TLS step checks the same name.

Both are the kind of bug a staged test surfaces quickly. When the log says exactly which name was sent and which roots were used, a wrong one is hard to miss.

Screenshot to comeredis-tls-os-keychain-roots.webp — the Redis connection editor's TLS fields in Verify full with a CA file set and "Also trust the OS keychain roots" ticked
The TLS fields on a Redis connection: a CA file adds to the built-in roots, and the OS store can be trusted too.

Limits

  • No live test yet against Azure Managed Redis, ElastiCache or MemoryDB.
  • Sentinel profiles refuse SSH tunnels, proxies and relay routes.
  • A passphrase-protected client key is refused with a clear message. Export an unencrypted PEM copy.
  • The Profiler and the RESP2 Pub/Sub fallback don't re-authenticate Entra mid-capture.

The reference is in Redis (Connection diagnostics) and TLS modes. Download SQLly, open a Redis connection, and press Test with the log open.