Skip to content

Commit 91c6777

Browse files
feat(redis): allow TLS SNI override for IP-based REDIS_URL
When trigger.dev's hosted workers reach our ElastiCache via PrivateLink, their REDIS_URL contains the VPCE-assigned IP, not a DNS name. Default ioredis TLS verification fails because the ElastiCache cert is issued for the cluster's DNS, not the IP. Add REDIS_TLS_SERVERNAME env var; when REDIS_URL is rediss:// + IP host, pass `tls: { servername }` to ioredis so cert hostname verification matches against the DNS name instead. Throws at client construction if REDIS_TLS_SERVERNAME is unset in this scenario (fail fast — no silent TLS bypass). No-op for in-VPC connections (DNS host), so the always-on Sim app keeps using default verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 67e18f7 commit 91c6777

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

apps/sim/lib/core/config/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const env = createEnv({
4848

4949
// Database & Storage
5050
REDIS_URL: z.string().url().optional(), // Redis connection string for caching/sessions
51+
REDIS_TLS_SERVERNAME: z.string().min(1).optional(), // TLS SNI override; required when REDIS_URL targets an IP over rediss:// (e.g. trigger.dev PrivateLink VPCE IP) so cert hostname verification matches the ElastiCache cert's CN
5152

5253
// Payment & Billing
5354
STRIPE_SECRET_KEY: z.string().min(1).optional(), // Stripe secret key for payment processing

apps/sim/lib/core/config/redis.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,35 @@ const logger = createLogger('Redis')
88

99
const redisUrl = env.REDIS_URL
1010

11+
/**
12+
* When REDIS_URL targets a bare IP over `rediss://` (e.g. trigger.dev's
13+
* PrivateLink VPCE IP), default TLS hostname verification fails — the cert
14+
* is issued for the ElastiCache DNS name, not the IP. Override SNI with
15+
* REDIS_TLS_SERVERNAME (set to the DNS the cert was issued for).
16+
*
17+
* For DNS hosts: no override needed, default verification works.
18+
*/
19+
function resolveTlsOptions(url: string | undefined): { servername: string } | undefined {
20+
if (!url) return undefined
21+
let parsed: URL
22+
try {
23+
parsed = new URL(url)
24+
} catch {
25+
return undefined
26+
}
27+
if (parsed.protocol !== 'rediss:') return undefined
28+
const hostIsIp = /^\d{1,3}(\.\d{1,3}){3}$/.test(parsed.hostname)
29+
if (!hostIsIp) return undefined
30+
if (!env.REDIS_TLS_SERVERNAME) {
31+
throw new Error(
32+
'REDIS_TLS_SERVERNAME must be set when REDIS_URL targets an IP over rediss://. ' +
33+
'TLS cert hostname verification cannot match an IP — set REDIS_TLS_SERVERNAME ' +
34+
'to the DNS name the cert was issued for (the ElastiCache primary endpoint).'
35+
)
36+
}
37+
return { servername: env.REDIS_TLS_SERVERNAME }
38+
}
39+
1140
let globalRedisClient: Redis | null = null
1241
let pingFailures = 0
1342
let pingInterval: NodeJS.Timeout | null = null
@@ -90,12 +119,15 @@ export function getRedisClient(): Redis | null {
90119
try {
91120
logger.info('Initializing Redis client')
92121

122+
const tls = resolveTlsOptions(redisUrl)
123+
93124
globalRedisClient = new Redis(redisUrl, {
94125
keepAlive: 1000,
95126
connectTimeout: 10000,
96127
commandTimeout: 5000,
97128
maxRetriesPerRequest: 5,
98129
enableOfflineQueue: true,
130+
...(tls ? { tls } : {}),
99131

100132
retryStrategy: (times) => {
101133
if (times > 10) {

0 commit comments

Comments
 (0)