Skip to content

Commit 869ede5

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 c403faf commit 869ede5

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
@@ -7,6 +7,35 @@ const logger = createLogger('Redis')
77

88
const redisUrl = env.REDIS_URL
99

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

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

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

0 commit comments

Comments
 (0)